/*

SUPERNOTE v1.0beta (c) 2005-2006 Angus Turnbull, http://www.twinhelix.com
Altering this notice or redistributing this file is prohibited.

*/

if(typeof addEvent!='function'){var addEvent=function(o,t,f,l){var d='addEventListener',n='on'+t,rO=o,rT=t,rF=f,rL=l;if(o[d]&&!l)return o[d](t,f,false);if(!o._evts)o._evts={};if(!o._evts[t]){o._evts[t]=o[n]?{b:o[n]}:{};o[n]=new Function('e','var r=true,o=this,a=o._evts["'+t+'"],i;for(i in a){o._f=a[i];r=o._f(e||window.event)!=false&&r;o._f=null}return r');if(t!='unload')addEvent(window,'unload',function(){removeEvent(rO,rT,rF,rL)})}if(!f._i)f._i=addEvent._i++;o._evts[t][f._i]=f};addEvent._i=1;var removeEvent=function(o,t,f,l){var d='removeEventListener';if(o[d]&&!l)return o[d](t,f,false);if(o._evts&&o._evts[t]&&f._i)delete o._evts[t][f._i]}}function cancelEvent(e,c){e.returnValue=false;if(e.preventDefault)e.preventDefault();if(c){e.cancelBubble=true;if(e.stopPropagation)e.stopPropagation()}};function SuperNote(myName,config){var defaults={myName:myName,allowNesting:false,cssProp:'visibility',cssVis:'inherit',cssHid:'hidden',IESelectBoxFix:true,showDelay:0,hideDelay:500,animInSpeed:0.1,animOutSpeed:0.1,animations:[],mouseX:0,mouseY:0,notes:{},rootElm:null,onshow:null,onhide:null};for(var p in defaults)this[p]=(typeof config[p]=='undefined')?defaults[p]:config[p];var obj=this;addEvent(document,'mouseover',function(evt){obj.mouseHandler(evt,1)});/*addEvent(document,'click',function(evt){obj.mouseHandler(evt,2)});*/addEvent(document,'mousemove',function(evt){obj.mouseTrack(evt)});addEvent(document,'mouseout',function(evt){obj.mouseHandler(evt,0)});this.instance=SuperNote.instances.length;SuperNote.instances[this.instance]=this}SuperNote.instances=[];SuperNote.prototype.bTypes={};SuperNote.prototype.pTypes={};SuperNote.prototype.pTypes.mouseoffset=function(obj,noteID,nextVis,nextAnim){with(obj){var note=notes[noteID];if(nextVis&&!note.animating&&!note.visible){note.ref.style.left=checkWinX(mouseX,note)+'px';note.ref.style.top=checkWinY(mouseY,note)+'px'}}};SuperNote.prototype.pTypes.mousetrack=function(obj,noteID,nextVis,nextAnim){with(obj){var note=notes[noteID];if(nextVis&&!note.animating&&!note.visible){var posString='with('+myName+'){var note=notes["'+noteID+'"];note.ref.style.left=checkWinX(mouseX,note)+"px";note.ref.style.top=checkWinY(mouseY,note)+"px"}';eval(posString);obj.IEFrameFix(noteID,1);if(!note.trackTimer)note.trackTimer=setInterval(posString,50)}else if(!nextVis&&!nextAnim){clearInterval(note.trackTimer);note.trackTimer=null}}};SuperNote.prototype.pTypes.triggeroffset=function(obj,noteID,nextVis,nextAnim){with(obj){var note=notes[noteID];if(nextVis&&!note.animating&&!note.visible){var x=0,y=0,elm=note.trigRef;while(elm){x+=elm.offsetLeft;y+=elm.offsetTop;elm=elm.offsetParent}note.ref.style.left=checkWinX(x,note)+'px';note.ref.style.top=checkWinY(y,note)+'px'}}};SuperNote.prototype.bTypes.pinned=function(obj,noteID,nextVis){with(obj){return(!nextVis)?false:true}};SuperNote.prototype.docBody=function(){return document[(document.compatMode&&document.compatMode.indexOf('CSS')>-1)?'documentElement':'body']};SuperNote.prototype.getWinW=function(){return this.docBody().clientWidth||window.innerWidth||0};SuperNote.prototype.getWinH=function(){return this.docBody().clientHeight||window.innerHeight||0};SuperNote.prototype.getScrX=function(){return this.docBody().scrollLeft||window.scrollX||0};SuperNote.prototype.getScrY=function(){return this.docBody().scrollTop||window.scrollY||0};SuperNote.prototype.checkWinX=function(newX,note){with(this){return Math.max(getScrX(),Math.min(newX,getScrX()+getWinW()-note.ref.offsetWidth-8))}};SuperNote.prototype.checkWinY=function(newY,note){with(this){return Math.max(getScrY(),Math.min(newY,getScrY()+getWinH()-note.ref.offsetHeight-8))}};SuperNote.prototype.mouseTrack=function(evt){with(this){mouseX=evt.pageX||evt.clientX+getScrX()||0;mouseY=evt.pageY||evt.clientY+getScrY()||0}};SuperNote.prototype.mouseHandler=function(evt,show){with(this){if(!document.documentElement)return true;var srcElm=evt.target||evt.srcElement,trigRE=new RegExp(myName+'-(hover|click)-([a-z0-9]+)','i'),targRE=new RegExp(myName+'-(note)-([a-z0-9]+)','i'),trigFind=1,foundNotes={};if(srcElm.nodeType!=1)srcElm=srcElm.parentNode;var elm=srcElm;while(elm&&elm!=rootElm){if(targRE.test(elm.id)||(trigFind&&trigRE.test(elm.className))){if(!allowNesting)trigFind=0;var click=RegExp.$1=='click'?1:0,noteID=RegExp.$2,ref=document.getElementById(myName+'-note-'+noteID),trigRef=trigRE.test(elm.className)?elm:null;if(ref){if(!notes[noteID]){notes[noteID]={click:click,ref:ref,trigRef:null,visible:0,animating:0,timer:null};ref._sn_obj=this;ref._sn_id=noteID}var note=notes[noteID];if(!note.click||(trigRef!=srcElm))foundNotes[noteID]=true;if(!note.click||(show==2)){if(trigRef)notes[noteID].trigRef=notes[noteID].ref._sn_trig=elm;display(noteID,show);if(note.click&&(srcElm==trigRef))cancelEvent(evt)}}}if(elm._sn_trig){trigFind=1;elm=elm._sn_trig}else{elm=elm.parentNode}}if(show==2)for(var n in notes){if(notes[n].click&&notes[n].visible&&!foundNotes[n])display(n,0)}}};SuperNote.prototype.display=function(noteID,show){with(this){with(notes[noteID]){clearTimeout(timer);if(!animating||(show?!visible:visible)){var tmt=animating?1:(show?showDelay||1:hideDelay||1);timer=setTimeout('SuperNote.instances['+instance+'].setVis("'+noteID+'",'+show+',false)',tmt)}}}};SuperNote.prototype.checkType=function(noteID,nextVis,nextAnim){with(this){var note=notes[noteID],bType,pType;if((/snp-([a-z]+)/).test(note.ref.className))pType=RegExp.$1;if((/snb-([a-z]+)/).test(note.ref.className))bType=RegExp.$1;if(nextAnim&&bType&&bTypes[bType]&&(bTypes[bType](this,noteID,nextVis)==false))return false;if(pType&&pTypes[pType])pTypes[pType](this,noteID,nextVis,nextAnim);return true}};SuperNote.prototype.setVis=function(noteID,show,now){with(this){var note=notes[noteID];if(note&&checkType(noteID,show,1)||now){note.visible=show;note.animating=1;animate(noteID,show,now)}}};SuperNote.prototype.animate=function(noteID,show,now){with(this){var note=notes[noteID];if(!note.animTimer)note.animTimer=0;if(!note.animC)note.animC=0;with(note){clearTimeout(animTimer);var speed=(animations.length&&!now)?(show?animInSpeed:animOutSpeed):1;if(show&&!animC){if(onshow)this.onshow(noteID);IEFrameFix(noteID,1);ref.style[cssProp]=cssVis}animC=Math.max(0,Math.min(1,animC+speed*(show?1:-1)));if(document.getElementById&&speed<1)for(var a=0;a<animations.length;a++)animations[a](ref,animC);if(!show&&!animC){if(onhide)this.onhide(noteID);IEFrameFix(noteID,0);ref.style[cssProp]=cssHid}if(animC!=parseInt(animC)){animTimer=setTimeout(myName+'.animate("'+noteID+'",'+show+')',50)}else{checkType(noteID,animC?1:0,0);note.animating=0}}}};SuperNote.prototype.IEFrameFix=function(noteID,show){with(this){if(!window.createPopup||!IESelectBoxFix)return;var note=notes[noteID],ifr=note.iframe;if(!ifr){ifr=notes[noteID].iframe=document.createElement('iframe');ifr.setAttribute("src", CS_BASE_URL + "/resources/blank.html");ifr.style.filter='progid:DXImageTransform.Microsoft.Alpha(opacity=0)';ifr.style.position='absolute';ifr.style.borderWidth='0';note.ref.parentNode.insertBefore(ifr,note.ref.parentNode.firstChild)}if(show){ifr.style.left=note.ref.offsetLeft+'px';ifr.style.top=note.ref.offsetTop+'px';ifr.style.width=note.ref.offsetWidth+'px';ifr.style.height=note.ref.offsetHeight+'px';ifr.style.visibility='inherit'}else{ifr.style.visibility='hidden'}}};


// This file provides the default namespaces for the Jive JavaScript Library

var HOSTURL = "/"
var AJAXPATH = "";

if(typeof(jive) == "undefined"){
    var jive = new Object();
    jive.gui = new Object();
    jive.model = new Object();
    jive.ext = new Object();
    jive.ext.y = new Object();
    jive.ext.x = new Object();
    jive.xml = new Object();
    jive.rte = new Object();
    jive.rte.macros = new Array();
}

if(typeof(console) == "undefined"){
    /*
    var win = window.open("",name,"width=250,height=700,scrollbars=1,resize=1");
    win.document.write("<html><head><title>History</title>" +
    "<style>ol{padding-left: 12px;} div{ font-family:verdana;font-size:8pt; margin-bottom:10px; }</style>" +
    "</head><body>");
    win.document.write("</body></html>");
    win.document.close();

    var arrayHolder = win.document.createElement('DIV');
    win.document.body.appendChild(arrayHolder);
    var arrayList = win.document.createElement('OL');
    arrayHolder.appendChild(arrayList);

    var log = win.document.createElement('DIV');
    win.document.body.appendChild(log);
    console = new Object();
    console.log = function(str){ log.appendChild(win.document.createTextNode(str)); log.appendChild(win.document.createElement('BR'));};
*/
    console = new Object();
    console.log = function(str){ };
}
if(typeof(console.debug) != "function"){
    console.debug = console.log;
}

// x_core.js
// X v3.15.1, Cross-Browser DHTML Library from Cross-Browser.com
// Copyright (c) 2002,2003,2004 Michael Foster (mike@cross-browser.com)
// This library is distributed under the terms of the LGPL (gnu.org)

// Variables:
jive.ext.x.xMac = (navigator.appVersion.indexOf('Mac') != -1);
jive.ext.x.xWindows = !jive.ext.x.xMac;
jive.ext.x.xVersion='3.15.1';
jive.ext.x.xNN4=false;
jive.ext.x.xOp7=false;
jive.ext.x.xOp5or6=false;
jive.ext.x.xIE4Up=false;
jive.ext.x.xIE4=false;
jive.ext.x.xIE5=false;
jive.ext.x.xUA=navigator.userAgent.toLowerCase();
jive.ext.x.xIE = false;
jive.ext.x.xSafari = false;
if(window.opera){
  jive.ext.x.xOp7=(jive.ext.x.xUA.indexOf('opera 7')!=-1 || jive.ext.x.xUA.indexOf('opera/7')!=-1);
  if (!jive.ext.x.xOp7) jive.ext.x.xOp5or6=(jive.ext.x.xUA.indexOf('opera 5')!=-1 || jive.ext.x.xUA.indexOf('opera/5')!=-1 || jive.ext.x.xUA.indexOf('opera 6')!=-1 || jive.ext.x.xUA.indexOf('opera/6')!=-1);
}
else if (document.all) {
  jive.ext.x.xIE4Up=jive.ext.x.xUA.indexOf('msie')!=-1 && parseInt(navigator.appVersion)>=4;
  jive.ext.x.xIE4=jive.ext.x.xUA.indexOf('msie 4')!=-1;
  jive.ext.x.xIE5=jive.ext.x.xUA.indexOf('msie 5')!=-1;
  jive.ext.x.xIE6=jive.ext.x.xUA.indexOf('msie 6')!=-1;
  jive.ext.x.xIE7=jive.ext.x.xUA.indexOf('msie 7')!=-1;
  jive.ext.x.xIE4Up=jive.ext.x.xIE4 || jive.ext.x.xIE5 || jive.ext.x.xIE6;
  jive.ext.x.xIE = true;
}
if(jive.ext.x.xUA.indexOf('safari') != -1 || jive.ext.x.xUA.indexOf('Safari') != -1){
  jive.ext.x.xSafari = true;
}
// Object:
jive.ext.x.xGetElementById = function(e,doc) {
  if(!$obj(doc)) doc = e.ownerDocument;
  if(e == null) return e;
  if(typeof(e)!='string') return e;
  if(doc.getElementById) e=doc.getElementById(e);
  else if(doc.all) e=doc.all[e];
  else e=null;
  return e;
}
jive.ext.x.xParent = function(e,bNode){
  if (!(e=jive.ext.x.xGetElementById(e))) return null;
  var p=null;
  if (!bNode && $def(e.offsetParent)) p=e.offsetParent;
  else if ($def(e.parentNode)) p=e.parentNode;
  else if ($def(e.parentElement)) p=e.parentElement;
  return p;
}
var $def = function(theItem) {
  return (typeof(theItem)!='undefined');
}
// yObj
// returns true if all the arguments are objects
var $obj = function(item)
{
  return (typeof(item) == 'object');
}
// yArr
// returns true if all the arguments are arrays
var $arr = function(item)
{
	return item != null && $obj(item) && $def(item.splice);
}
$str = function(s) {
  return typeof(s)=='string';
}
var $num = function(n) {
  return typeof(n)=='number';
}
// Appearance:
jive.ext.x.xShow = function(e) {
  if(!(e=jive.ext.x.xGetElementById(e))) return;
  if(e.style && $def(e.style.visibility)) e.style.visibility='visible';
}
jive.ext.x.xHide = function(e) {
  if(!(e=jive.ext.x.xGetElementById(e))) return;
  if(e.style && $def(e.style.visibility)) e.style.visibility='hidden';
}
jive.ext.x.xDisplay = function(e,s)
{
  if(!(e=jive.ext.x.xGetElementById(e))) return null;
  if(e.style && $def(e.style.display)) {
    if ($str(s)) e.style.display = s;
    return e.style.display;
  }
  return null;
}
jive.ext.x.xDisplayNone = function(e) {
  if(!(e=jive.ext.x.xGetElementById(e))) return;
  if(e.style && $def(e.style.display)) e.style.display='none';
}
jive.ext.x.xDisplayBlock = function(e) {
  if(!(e=jive.ext.x.xGetElementById(e))) return;
  if(e.style && $def(e.style.display)) e.style.display='block';
}
jive.ext.x.xDisplayInline = function(e) {
  if(!(e=jive.ext.x.xGetElementById(e))) return;
  if(e.style && $def(e.style.display)) e.style.display='inline';
}
// xVisibility, Copyright 2003-2005 Michael Foster (Cross-Browser.com)
// Part of X, a Cross-Browser Javascript Library, Distributed under the terms of the GNU LGPL

jive.ext.x.xZIndex = function(e,uZ)
{
  if(!(e=jive.ext.x.xGetElementById(e))) return 0;
  if(e.style && $def(e.style.zIndex)) {
    if($num(uZ)) e.style.zIndex=uZ;
    uZ=parseInt(e.style.zIndex);
  }
  return uZ;
}
// Position:
jive.ext.x.xMoveTo = function(e,iX,iY) {
  jive.ext.x.xLeft(e,iX);
  jive.ext.x.xTop(e,iY);
}
jive.ext.x.xLeft = function(e,iX) {
  if(!(e=jive.ext.x.xGetElementById(e))) return 0;
  var css=$def(e.style);
  if (css && $str(e.style.left)) {
    if($num(iX)) e.style.left=iX+'px';
    else {
      iX=parseInt(e.style.left);
      if(isNaN(iX)) iX=0;
    }
  }
  else if(css && $def(e.style.pixelLeft)) {
    if($num(iX)) e.style.pixelLeft=iX;
    else iX=e.style.pixelLeft;
  }
  return iX;
}
jive.ext.x.xTop = function(e,iY) {
  if(!(e=jive.ext.x.xGetElementById(e))) return 0;
  var css=$def(e.style);
  if(css && $str(e.style.top)) {
    if($num(iY)) e.style.top=iY+'px';
    else {
      iY=parseInt(e.style.top);
      if(isNaN(iY)) iY=0;
    }
  }
  else if(css && $def(e.style.pixelTop)) {
    if($num(iY)) e.style.pixelTop=iY;
    else iY=e.style.pixelTop;
  }
  return iY;
}
jive.ext.x.xPageX = function(obj) {
    var curleft = 0;
    if(obj.offsetParent)
        while(1)
        {
          curleft += obj.offsetLeft;
          if(!obj.offsetParent)
            break;
          obj = obj.offsetParent;
        }
    else if(obj.x)
        curleft += obj.x;
    return curleft;
  }

jive.ext.x.xPageY = function(obj){
    var curtop = 0;
    if(obj.offsetParent)
        while(1)
        {
          curtop += obj.offsetTop;
          if(!obj.offsetParent)
            break;
          obj = obj.offsetParent;
        }
    else if(obj.y)
        curtop += obj.y;
    return curtop;
}
jive.ext.x.xScrollLeft = function(e) {
  var offset=0, doc = e.ownerDocument;
  if (!(e=jive.ext.x.xGetElementById(e))) {
    if(doc.documentElement && doc.documentElement.scrollLeft) offset=doc.documentElement.scrollLeft;
    else if(doc.body && $def(doc.body.scrollLeft)) offset=doc.body.scrollLeft;
  }
  else { if ($num(e.scrollLeft)) offset = e.scrollLeft; }
  return offset;
}
jive.ext.x.xScrollTop = function(e) {
  var offset=0, doc = e.ownerDocument;
  if (!(e=jive.ext.x.xGetElementById(e))) {
    if(doc.documentElement && doc.documentElement.scrollTop) offset=doc.documentElement.scrollTop;
    else if(doc.body && $def(doc.body.scrollTop)) offset=doc.body.scrollTop;
  }
  else { if ($num(e.scrollTop)) offset = e.scrollTop; }
  return offset;
}
jive.ext.x.xWidth = function(e,w)
{
  if(!(e=jive.ext.x.xGetElementById(e))) return 0;
  if ($num(w)) {
    if (w<0) w = 0;
    else w=Math.round(w);
  }
  else w=-1;
  var css=$def(e.style);
  if (e == document || e.tagName.toLowerCase() == 'html' || e.tagName.toLowerCase() == 'body') {
    w = jive.ext.x.xClientWidth();
  }
  else if(css && $def(e.offsetWidth) && $str(e.style.width)) {
    if(w>=0) {
      var pl=0,pr=0,bl=0,br=0;
      if (document.compatMode=='CSS1Compat') {
        var gcs = jive.ext.x.xGetCS;
        pl=gcs(e,'padding-left',1);
        if (pl !== null) {
          pr=gcs(e,'padding-right',1);
          bl=gcs(e,'border-left-width',1);
          br=gcs(e,'border-right-width',1);
        }
        // Should we try this as a last resort?
        // At this point getComputedStyle and currentStyle do not exist.
        else if($def(e.offsetWidth,e.style.width)){
          e.style.width=w+'px';
          pl=e.offsetWidth-w;
        }
      }
      w-=(pl+pr+bl+br);
      if(isNaN(w)||w<0) return;
      else e.style.width=w+'px';
    }
    w=e.offsetWidth;
  }
  else if(css && $def(e.style.pixelWidth)) {
    if(w>=0) e.style.pixelWidth=w;
    w=e.style.pixelWidth;
  }
  return w;
}
jive.ext.x.xCamelize = function(cssPropStr)
{
  var i, c, a = cssPropStr.split('-');
  var s = a[0];
  for (i=1; i<a.length; ++i) {
    c = a[i].charAt(0);
    s += a[i].replace(c, c.toUpperCase());
  }
  return s;
}
jive.ext.x.xGetCS = function(e, p)
{
    try{
      if(!(e=jive.ext.x.xGetElementById(e))) return null;
      var s, v = 'undefined', dv = e.ownerDocument.defaultView;
      if(dv && dv.getComputedStyle){
        s = dv.getComputedStyle(e,'');
        if (s) v = s.getPropertyValue(p);
      }
      else if(e.currentStyle) {
        v = e.currentStyle[jive.ext.x.xCamelize(p)];
      }
      else return null;
        if($str(v) && v.indexOf("px") > 0){
            v = v.substr(0,v.indexOf("px"));
            v = parseInt(v);
        }
      return v;
    }catch(e){
        return "";
    }
}
jive.ext.x.xSetCH = function(ele,uH){
  var pt=0,pb=0,bt=0,bb=0,doc = ele.ownerDocument
  if($def(doc.defaultView) && $def(doc.defaultView.getComputedStyle)){
    pt=jive.ext.x.xGetCS(ele,'padding-top');
    pb=jive.ext.x.xGetCS(ele,'padding-bottom');
    bt=jive.ext.x.xGetCS(ele,'border-top-width');
    bb=jive.ext.x.xGetCS(ele,'border-bottom-width');
  }
  else if($def(ele.currentStyle,doc.compatMode)){
    if(doc.compatMode=='CSS1Compat'){
      pt=parseInt(ele.currentStyle.paddingTop);
      pb=parseInt(ele.currentStyle.paddingBottom);
      bt=parseInt(ele.currentStyle.borderTopWidth);
      bb=parseInt(ele.currentStyle.borderBottomWidth);
    }
  }
  else if($def(ele.offsetHeight,ele.style.height)){ // ?
    ele.style.height=uH+'px';
    pt=ele.offsetHeight-uH;
  }
  if(isNaN(pt)) pt=0; if(isNaN(pb)) pb=0; if(isNaN(bt)) bt=0; if(isNaN(bb)) bb=0;
  var cssH=uH-(pt+pb+bt+bb);
  if(isNaN(cssH)||cssH<0) return;
  else ele.style.height=cssH+'px';
}
jive.ext.x.xHeight = function(e,uH) {
  if(!(e=jive.ext.x.xGetElementById(e))) return 0;
  if ($num(uH)) {
    if (uH<0) uH = 0;
    else uH=Math.round(uH);
  }
  else uH=0;
  var css=$def(e.style);
  if(css && $def(e.offsetHeight) && $str(e.style.height)) {
    if(uH) jive.ext.x.xSetCH(e, uH);
    uH=e.offsetHeight;
  }
  else if(css && $def(e.style.pixelHeight)) {
    if(uH) e.style.pixelHeight=uH;
    uH=e.style.pixelHeight;
  }
  return uH;
}
jive.ext.x.xHasPoint = function(ele, iLeft, iTop, iClpT, iClpR, iClpB, iClpL) {
  if (!$num(iClpT)){iClpT=iClpR=iClpB=iClpL=0;}
  else if (!$num(iClpR)){iClpR=iClpB=iClpL=iClpT;}
  else if (!$num(iClpB)){iClpL=iClpR; iClpB=iClpT;}
  var thisX = jive.ext.x.xPageX(ele), thisY = jive.ext.x.xPageY(ele);
  return (iLeft >= thisX + iClpL && iLeft <= thisX + jive.ext.x.xWidth(ele) - iClpR &&
          iTop >=thisY + iClpT && iTop <= thisY + jive.ext.x.xHeight(ele) - iClpB );
}
// Window:
jive.ext.x.xClientWidth = function() {
  var w=0;
  if(jive.ext.x.xOp5or6) w=window.innerWidth;
  else if(!window.opera && document.documentElement && document.documentElement.clientWidth) // v3.12
    w=document.documentElement.clientWidth;
  else if(document.body && document.body.clientWidth)
    w=document.body.clientWidth;
  else if($def(window.innerWidth,window.innerHeight,document.height)) {
    w=window.innerWidth;
    if(document.height>window.innerHeight) w-=16;
  }
  return w;
}
jive.ext.x.xClientHeight = function() {
  var h=0;
  if(jive.ext.x.xOp5or6) h=window.innerHeight;
  else if(!window.opera && document.documentElement && document.documentElement.clientHeight) // v3.12
    h=document.documentElement.clientHeight;
  else if(document.body && document.body.clientHeight)
    h=document.body.clientHeight;
  else if($def(window.innerWidth,window.innerHeight,document.width)) {
    h=window.innerHeight;
    if(document.width>window.innerWidth) h-=16;
  }
  return h;
}



jive.ext.x.xDocHeight = function(doc) {
    if(doc)
        var b=doc.body, e=doc.documentElement;
    else
        var b=document.body, e=document.documentElement;
    var esh=0, eoh=0, bsh=0, boh=0;
    if (e) {
        esh = e.scrollHeight;
        eoh = e.offsetHeight;
    }
    if (b) {
        bsh = b.scrollHeight;
        boh = b.offsetHeight;
    }
    return Math.max(esh,eoh,bsh,boh);
}

// x_event.js
// X v3.15, Cross-Browser DHTML Library from Cross-Browser.com
// Copyright (c) 2002,2003,2004 Michael Foster (mike@cross-browser.com)
// This library is distributed under the terms of the LGPL (gnu.org)

jive.ext.x.xAddEventListener = function(e,eventType,eventListener,useCapture) {
  if(!(e=jive.ext.x.xGetElementById(e))) return;
  eventType=eventType.toLowerCase();
  if((!jive.ext.x.xIE4Up && !jive.ext.x.xOp7) && e==window) {
    if(eventType=='resize') { jive.ext.x.xPCW=jive.ext.x.xClientWidth(); jive.ext.x.xPCH=jive.ext.x.xClientHeight(); jive.ext.x.xREL=eventListener; jive.ext.x.xResizeEvent(); return; }
    if(eventType=='scroll') { jive.ext.x.xPSL=jive.ext.x.xScrollLeft(); jive.ext.x.xPST=jive.ext.x.xScrollTop(); jive.ext.x.xSEL=eventListener; jive.ext.x.xScrollEvent(); return; }
  }
  if(e.addEventListener) e.addEventListener(eventType,eventListener,useCapture);
  else if(e.attachEvent) e.attachEvent('on'+eventType,eventListener);
  else if(e.captureEvents) {
    if(useCapture||(eventType.indexOf('mousemove')!=-1)) { e.captureEvents(eval('Event.'+eventType.toUpperCase())); }
    var eh='e.on'+eventType+'=eventListener';
    eval(eh);
  }
  else{
	var eh='e.on'+eventType+'=eventListener';
  	eval(eh);
  }
}
jive.ext.x.xRemoveEventListener = function(e,eventType,eventListener,useCapture) {
  if(!(e=jive.ext.x.xGetElementById(e))) return;
  eventType=eventType.toLowerCase();
  if((!jive.ext.x.xIE4Up && !jive.ext.x.xOp7) && e==window) {
    if(eventType=='resize') { jive.ext.x.xREL=null; return; }
    if(eventType=='scroll') { jive.ext.x.xSEL=null; return; }
  }
  var eh='e.on'+eventType+'=null';
  if(e.removeEventListener) e.removeEventListener(eventType,eventListener,useCapture);
  else if(e.detachEvent) e.detachEvent('on'+eventType,eventListener);
  else if(e.releaseEvents) {
    if(useCapture||(eventType.indexOf('mousemove')!=-1)) { e.releaseEvents(eval('Event.'+eventType.toUpperCase())); }
    eval(eh);
  }
  else eval(eh);
}
// xStopPropagation, Copyright 2004-2006 Michael Foster (Cross-Browser.com)
// Part of X, a Cross-Browser Javascript Library, Distributed under the terms of the GNU LGPL

jive.ext.x.xStopPropagation = function(evt)
{
  if (evt && evt.stopPropagation) evt.stopPropagation();
  else if (window.event) window.event.cancelBubble = true;
}
jive.ext.x.xEvent = function(evt) { // cross-browser event object prototype
  this.type = '';
  this.target = null;
  this.keyCode = 0;
  var e = evt ? evt : window.event;
  if(!e) return;
  if(e.type) this.type = e.type;
  if(e.target) this.target = e.target;
  else if(e.srcElement) this.target = e.srcElement;

  var pageX = null;
  var pageY = null;

  this.pageX = function(){
  	if(pageX == null){
		if(jive.ext.x.xOp5or6) { pageX = e.clientX; pageY = e.clientY; }
		else if(jive.ext.x.xDef(e.clientX,e.clientY)) { pageX = e.clientX + jive.ext.x.xScrollLeft(); pageY = e.clientY + jive.ext.x.xScrollTop(); }
  	}
  	return pageX;
  }

  this.pageY = function(){
  	if(pageY == null){
		if(jive.ext.x.xOp5or6) { pageX = e.clientX; pageY = e.clientY; }
		else if(jive.ext.x.xDef(e.clientX,e.clientY)) { pageX = e.clientX + jive.ext.x.xScrollLeft(); pageY = e.clientY + jive.ext.x.xScrollTop(); }
  	}
  	return pageY;
  }

  var offsetX = null;
  var offsetY = null;
  this.offsetX = function(){
  	if(offsetX == null){
		if(jive.ext.x.xDef(e.layerX,e.layerY)) { offsetX = e.layerX; offsetY = e.layerY; }
		else if(jive.ext.x.xDef(e.offsetX,e.offsetY)) { offsetX = e.offsetX; offsetY = e.offsetY; }
		else { offsetX = this.pageX - jive.ext.x.xPageX(this.target); offsetY = this.pageY - jive.ext.x.xPageY(this.target); }
  	}
  	return offsetX;
  }

  this.offsetY = function(){
  	if(offsetY == null){
		if(jive.ext.x.xDef(e.layerX,e.layerY)) { offsetX = e.layerX; offsetY = e.layerY; }
		else if(jive.ext.x.xDef(e.offsetX,e.offsetY)) { offsetX = e.offsetX; offsetY = e.offsetY; }
		else { offsetX = this.pageX - jive.ext.x.xPageX(this.target); offsetY = this.pageY - jive.ext.x.xPageY(this.target); }
  	}
  	return offsetY;
  }

  if (e.keyCode) { this.keyCode = e.keyCode; } // for moz/fb, if keyCode==0 use which
  else if (jive.ext.x.xDef(e.which)) { this.keyCode = e.which; }
}
jive.ext.x.xResizeEvent = function() { // window resize event simulation
  if (jive.ext.x.xREL) setTimeout('jive.ext.x.xResizeEvent()', 250);
  var cw = jive.ext.x.xClientWidth(), ch = jive.ext.x.xClientHeight();
  if (jive.ext.x.xPCW != cw || jive.ext.x.xPCH != ch) { jive.ext.x.xPCW = cw; jive.ext.x.xPCH = ch; if (jive.ext.x.xREL) jive.ext.x.xREL(); }
}
jive.ext.x.xScrollEvent = function() { // window scroll event simulation
  if (jive.ext.x.xSEL) setTimeout('jive.ext.x.xScrollEvent()', 250);
  var sl = jive.ext.x.xScrollLeft(), st = jive.ext.x.xScrollTop();
  if (jive.ext.x.xPSL != sl || jive.ext.x.xPST != st) { jive.ext.x.xPSL = sl; jive.ext.x.xPST = st; if (jive.ext.x.xSEL) jive.ext.x.xSEL(); }
}
// end x_event.js




// x_timer.js
// X v3.15, Cross-Browser DHTML Library from Cross-Browser.com
// Copyright (c) 2002,2003,2004 Michael Foster (mike@cross-browser.com)
// This library is distributed under the terms of the LGPL (gnu.org)

jive.ext.x.xTimerMgr = function() // object prototype
{
  // Public Methods
  this.set = function(type, obj, sMethod, uTime, data) // type: 'interval' or 'timeout'
  {
    return (this.timers[this.timers.length] = new xTimerObj(type, obj, sMethod, uTime, data));
  }
  // Private Properties
  this.timers = new Array();
  // Private Methods
  this.running = false;
  this.run = function()
  {
    if(this.running) return;
    this.running = true;
    var i, t, d = new Date(), now = d.getTime();
    for (i = 0; i < this.timers.length; ++i) {
      t = this.timers[i];
      if (t && t.running) {
        t.elapsed = now - t.time0;
        if (t.elapsed >= t.preset) { // timer event on t
          t.obj[t.mthd](t); // pass listener this xTimerObj
          if (t.type.charAt(0) == 'i') { t.time0 = now; }
          else { t.stop(); }
        }
      }
    }
    this.running = false;
  }
  // Private Object Prototype
  function xTimerObj(type, obj, mthd, preset, data)
  {
    // Public Methods
    this.stop = function() { this.running = false; }
    this.start = function() { this.running = true; } // continue after a stop
    this.reset = function()
    {
      var d = new Date();
      this.time0 = d.getTime();
      this.elapsed = 0;
      this.running = true;
    }
    // Public Properties
    this.data = data;
    // Read-only Properties
    this.type = type; // 'interval' or 'timeout'
    this.obj = obj;
    this.mthd = mthd; // string
    this.preset = preset;
    this.reset();
  } // end xTimerObj
} // end xTimerMgr

jive.ext.x.xTimer = new jive.ext.x.xTimerMgr(); // applications assume global name is 'xTimer'
jive.ext.x.xAddEventListener(window, "load", function(){
    setInterval('jive.ext.x.xTimer.run()', 250);
})

// end x_timer.js



// The remaining functions are utility functions added by Adam Wulf
// on June of 2004

/**
 * store an object in a hashtable.
 * hash key must be provided
 * currently O(1) for insert
 * O(n) for retrive/clear
 * we can optimize this later
 *
 * ex
 * var cal = new jotlet.model.Calendar();
 * var key = 224451 // any key you want to use to save/lookup the cal object
 * var table = new jotlet.external.y.HashTable();
 * table.put(key, cal);
 * table.get(key) // gives us back the cal object
 */
jive.ext.y.HashTable = function(){
	var that = this;

	var count = 0;

	this.getCount = function(){
		return count;
	}

	this.undefined = new Object();

	this.cache = new Array();

	/**
	 * add an item to the hashtable
	 */
	this.put = function(index, item){
		that.clear(index);
		that.cache[index] = item;
		count = count + 1;
	}

	/**
	 * retrieve an item from the hashtable
	 */
	this.get = function(index){
		if(typeof(that.cache[index])!='undefined' && that.cache[index] != that.undefined){
			return that.cache[index];
		}else{
			return false;
		}
	}

	/**
	 * clear the hashtable
	 */
	this.clear = function(index){
		if(that.cache[index] != that.undefined &&
		   that.cache[index] != null){
			   // decrease count only if we're
			   // clearing a legit item
			   count = count - 1;
		}
		that.cache[index] = that.undefined;
	}

	/**
	 * return an array of this Hashtable
	 * (values only, no keys)
	 */
	this.toArray = function(func){
		var a = new Array();
		for (var i in that.cache){
			if(typeof(func) == "function" &&
			   func(that.cache[i])){
			   	a.push(that.cache[i]);
			}else
			if(typeof(func) != "function" &&
			      that.cache[i] != that.undefined &&
				that.cache[i] != Array.prototype.find &&
				that.cache[i] != Array.prototype.jFind &&
				that.cache[i] != Array.prototype.remove &&
				that.cache[i] != Array.prototype.______array &&
				that.cache[i] != null){
				a.push(that.cache[i]);
			}
		}
		return a;
	}

	/**
	 * return the keys of a Hashtable
	 */
	this.toKeysArray = function(func){
		var a = new Array();
		for (var i in that.cache){
			a.push(i);
		}
		return i;
	}
}


jive.ext.y.yBottom = function(e,iY) {
  if(!(e=jive.ext.x.xGetElementById(e))) return 0;
  var css=$def(e.style);
  if(css && $str(e.style.bottom)) {
    if($num(iY)) e.style.bottom=iY+'px';
    else {
      iY=parseInt(e.style.bottom);
      if(isNaN(iY)) iY=0;
    }
  }
  else if(css && $def(e.style.pixelBottom)) {
    if($num(iY)) e.style.pixelBottom=iY;
    else iY=e.style.pixelBottom;
  }
  return iY;
}


/**
 * opacity is between 0-100
 */
jive.ext.y.yOpacity = function(e, op){
  if (!(e=jive.ext.x.xGetElementById(e))) return;
  if (jotlet.external.x.xNum(op)) {
    if (op<0) op = 0;
    else if(op > 100) op = 100;
    else op=Math.round(op);

    if(jotlet.external.x.xDef(e.style.MozOpacity)){
	  e.style.MozOpacity = (op/100.0);
	  return e.style.MozOpacity * 100.0;
    }
    if(jotlet.external.x.xDef(e.style.opacity)){
	  e.style.opacity = (op/100.0);
	  return e.style.opacity * 100.0;
    }

    if(jotlet.external.x.xStr(e.style.filter)){ // ie
//    if(jotlet.external.x.xDef(e.filters) && jotlet.external.x.xDef(e.filters.alpha) && jotlet.external.x.xDef(e.filters.alpha.opacity)){
    	e.style.filter = 'alpha(opacity=' + (op) + ')';
//	e.filters.alpha.opacity = op;
	e.yOpacity = op;
	return e.yOpacity;
    }
  }
  if(jotlet.external.x.xDef(e.style.MozOpacity)){
	  return e.style.MozOpacity * 100.0;
  }
  if(jotlet.external.x.xDef(e.style.opacity)){
	  return e.style.opacity * 100.0;
  }
  if(jotlet.external.x.xDef(e.filters) && jotlet.external.x.xDef(e.filters.alpha) && jotlet.external.x.xDef(e.filters.alpha.opacity)){
	  return e.filters.alpha.opacity;
  }
}



// var ajax = new yAjax("index.php");
// ajax.POST(parameters);


// url is a string
jive.ext.y.yAjax = function(rdyFun, errFun){
	var http_request = false;

	if (window.XMLHttpRequest) { // Mozilla, Safari,...
		http_request = new XMLHttpRequest();
		if (http_request.overrideMimeType) {
			http_request.overrideMimeType('text/xml');
		}
	} else if (window.ActiveXObject) { // IE
		try {
			http_request = new ActiveXObject("Msxml2.XMLHTTP");
		} catch (e) {
			try {
				http_request = new ActiveXObject("Microsoft.XMLHTTP");
			} catch (e) {}
		}
	}
	if (!http_request) {
		return false;
	}

	http_request.onreadystatechange = alertContents;

	function alertContents() {
		try{
			if (http_request.readyState == 4) {
				if (http_request.status == 200) {
					rdyFun(http_request.responseText);
				} else {
					errFun();
				}
			}
		}catch(e){
			// alert(e);
		}
	}

	// parameters is a form
	this.POST = function (url, parameters) {
			http_request.open('POST', url, true);
			http_request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
			http_request.setRequestHeader("Content-length", parameters.length);
			http_request.setRequestHeader("Connection", "close");
			http_request.send(parameters);
	}
}


// Ver .91 Feb 21 1998
//////////////////////////////////////////////////////////////
//
//	Copyright 1998 Jeremie
//	Free for public non-commercial use and modification
//	as long as this header is kept intact and unmodified.
//	Please see http://www.jeremie.com for more information
//	or email jer@jeremie.com with questions/suggestions.
//
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
////////// Simple XML Processing Library //////////////////////
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
////   Fully complies to the XML 1.0 spec
////   as a well-formed processor, with the
////   exception of full error reporting and
////   the document type declaration(and it's
///   related features, internal entities, etc).
///////////////////////////////////////////////////////////////

// global vars to track element UID's for the index

jive.xml._Xparse_count = 0;
jive.xml._Xparse_index = new Array();

//////////////////////
//// util to replace internal entities in input string
jive.xml._entity = function(str)
{
	var A = new Array();

	A = str.split("&l" + "t;");
	str = A.join("<");
	A = str.split("&g" + "t;");
	str = A.join(">");
	A = str.split("&qu" + "ot;");
	str = A.join("\"");
	A = str.split("&ap" + "os;");
	str = A.join("\'");
	A = str.split("&a" + "mp;");
	str = A.join("&");

	return str;
}
/////////////////////////
//////////////////////
//// util to remove white characters from input string
jive.xml._strip = function(str)
{
	var A = new Array();

	A = str.split("\n");
	str = A.join("");
	A = str.split(" ");
	str = A.join("");
	A = str.split("\t");
	str = A.join("");
	return str;
}

//////////////////////
//// util to replace white characters in input string
jive.xml._normalize = function(str)
{
	var A = new Array();

	A = str.split("\n");
	str = A.join(" ");
	A = str.split("\t");
	str = A.join(" ");
	return str;
}
//////////////////
/////////////////////////
//// the object constructors for the hybrid DOM
jive.xml._element = function()
{
	this.type = "element";
	this.tagName = new String();
	this.attributes = new Array();
	this.childNodes = new Array();
	this.nodeValue = "";
	this.uid = jive.xml._Xparse_count++;
	jive.xml._Xparse_index[this.uid]=this;
}

jive.xml._chardata = function()
{
	this.type = "chardata";
	this.value = new String();
}

jive.xml._pi = function()
{
	this.type = "pi";
	this.value = new String();
}

jive.xml._comment = function()
{
	this.type = "comment";
	this.value = new String();
}

// an internal fragment that is passed between functions
jive.xml._frag = function()
{
	this.str = new String();
	this.ary = new Array();
	this.end = new String();
}

/////////////////////////

///////////////////////
//// functions to process different tags

jive.xml._tag_element = function(frag)
{
	// initialize some temporary variables for manipulating the tag
	var close = frag.str.indexOf(">");
	var empty = (frag.str.substring(close - 1,close) == "/");
	if(empty)
	{
		close -= 1;
	}

	// split up the name and attributes
	var starttag = jive.xml._normalize(frag.str.substring(1,close));
	var nextspace = starttag.indexOf(" ");
	var attribs = new String();
	var name = new String();
	if(nextspace != -1)
	{
		name = starttag.substring(0,nextspace);
		attribs = starttag.substring(nextspace + 1,starttag.length);
	}
	else
	{
		name = starttag;
	}

	var thisary = frag.ary.length;
	frag.ary[thisary] = new jive.xml._element();
	frag.ary[thisary].tagName = jive.xml._strip(name);
	if(attribs.length > 0)
	{
		frag.ary[thisary].attributes = jive.xml._attribution(attribs);
	}
	if(!empty)
	{
		// !!!! important,
		// take the contents of the tag and parse them
		var contents = new jive.xml._frag();
		contents.str = frag.str.substring(close + 1,frag.str.length);
		contents.end = name;
		var val = contents;
		contents = jive.xml._compile(contents);
		frag.ary[thisary].childNodes = contents.ary;
		frag.ary[thisary].nodeValue = val;
		frag.str = contents.str;
	}
	else
	{
		frag.str = frag.str.substring(close + 2,frag.str.length);
	}
	return frag;
}

jive.xml._tag_pi = function(frag)
{
	var close = frag.str.indexOf("?" + ">");
	var val = frag.str.substring(2,close);
	var thisary = frag.ary.length;
	frag.ary[thisary] = new jive.xml._pi();
	frag.ary[thisary].nodeValue = val;
	frag.str = frag.str.substring(close + 2,frag.str.length);
	return frag;
}


jive.xml._tag_comment = function(frag)
{
	var close = frag.str.indexOf("--" + ">");
	var val = frag.str.substring(4,close);
	var thisary = frag.ary.length;
	frag.ary[thisary] = new jive.xml._comment();
	frag.ary[thisary].nodeValue = val;
	frag.str = frag.str.substring(close + 3,frag.str.length);
	return frag;
}

jive.xml._tag_cdata = function(frag)
{
	var close = frag.str.indexOf("]" + "]>");
	var val = frag.str.substring(9,close);
	var thisary = frag.ary.length;
	frag.ary[thisary] = new jive.xml._chardata();
	frag.ary[thisary].nodeValue = val;
	frag.str = frag.str.substring(close + 3,frag.str.length);
	return frag;
}

/////////////////////////


//////////////////
//// util for element attribute parsing
//// returns an array of all of the keys = values
jive.xml._attribution = function(str)
{
	var all = new Array();
	while(1)
	{
		var eq = str.indexOf("=");
		if(str.length == 0 || eq == -1)
		{
			return all;
		}

		var id1 = str.indexOf("\'");
		var id2 = str.indexOf("\"");
		var ids = new Number();
		var id = new String();
		if((id1 < id2 && id1 != -1) || id2 == -1)
		{
			ids = id1;
			id = "\'";
		}
		if((id2 < id1 || id1 == -1) && id2 != -1)
		{
			ids = id2;
			id = "\"";
		}
		var nextid = str.indexOf(id,ids + 1);
		var val = str.substring(ids + 1,nextid);

		var name = jive.xml._strip(str.substring(0,eq));
		all[name] = jive.xml._entity(val);
		str = str.substring(nextid + 1,str.length);
	}
	return "";
}

////////////////////

/////////////////////////
//// transforms raw text input into a multilevel array
jive.xml._compile = function(frag)
{
	// keep circling and eating the str
	while(1)
	{
		// when the str is empty, return the fragment
		if(frag.str.length == 0)
		{
			return frag;
		}

		var TagStart = frag.str.indexOf("<");

		if(TagStart != 0)
		{
			// theres a chunk of characters here, store it and go on
			var thisary = frag.ary.length;
			frag.ary[thisary] = new jive.xml._chardata();
			if(TagStart == -1)
			{
				frag.ary[thisary].nodeValue = jive.xml._entity(frag.str);
				frag.str = "";
			}
			else
			{
				frag.ary[thisary].nodeValue = jive.xml._entity(frag.str.substring(0,TagStart));
				frag.str = frag.str.substring(TagStart,frag.str.length);
			}
		}
		else
		{
			// determine what the next section is, and process it
			if(frag.str.substring(1,2) == "?")
			{
				frag = jive.xml._tag_pi(frag);
			}
			else
			{
				if(frag.str.substring(1,4) == "!" + "--")
				{
					frag = jive.xml._tag_comment(frag);
				}
				else
				{
					if(frag.str.substring(1,9) == "!" + "[CDA" + "TA[")
					{
						frag = jive.xml._tag_cdata(frag);
					}
					else
					{
						if(frag.str.substring(1,frag.end.length + 3) == "/" + frag.end + ">" || jive.xml._strip(frag.str.substring(1,frag.end.length + 3)) == "/" + frag.end)
						{
							// found the end of the current tag, end the recursive process and return
							frag.str = frag.str.substring(frag.end.length + 3,frag.str.length);
							frag.end = "";
							return frag;
						}
						else
						{
							frag = jive.xml._tag_element(frag);
						}
					}
				}
			}
		}
	}
	return "";
}

///////////////////////


//////////////////////
//// util to remove \r characters from input string
//// and return xml string without a prolog
jive.xml._prolog = function(str)
{
	var A = new Array();

	A = str.split("\r\n");
	str = A.join("\n");
	A = str.split("\r");
	str = A.join("\n");

	var start = str.indexOf("<");
	if(str.substring(start,start + 3) == "<" + "?x" || str.substring(start,start + 3) == "<" + "?X" )
	{
		var close = str.indexOf("?" + ">");
		str = str.substring(close + 2,str.length);
	}
	var start = str.indexOf("<!DOC" + "TYPE");
	if(start != -1)
	{
		var close = str.indexOf(">",start) + 1;
		var dp = str.indexOf("[",start);
		if(dp < close && dp != -1)
		{
			close = str.indexOf("]" + ">",start) + 2;
		}
		str = str.substring(close,str.length);
	}
	return str;
}


//// Main public function that is called to
//// parse the XML string and return a root element object
jive.xml.Xparse = function(src)
{
	var frag = new jive.xml._frag();
	// remove bad \r characters and the prolog
	frag.str = jive.xml._prolog(src);
	// create a root element to contain the document
	var root = new Object();
	// main recursive function to process the xml
	frag = jive.xml._compile(frag);
	// all done, lets return the root element + index + document
	if(frag.ary.length > 0){
		root.documentElement = frag.ary[0];
	}else{
		root.documentElement = null;
	}
	root.tagName = "RO" + "OT";
	root.index = jive.xml._Xparse_index;
	jive.xml._Xparse_index = new Array();
	return root;
}

/////////////////////////

//////////////////////////////////////////////////////////////

//	End Copyright 1998 Jeremie

//////////////////////////////////////////////////////////////




// Ver .91 Feb 21 1998
//////////////////////////////////////////////////////////////
//
//	Copyright 1998 Jeremie
//	Free for public non-commercial use and modification
//	as long as this header is kept intact and unmodified.
//	Please see http://www.jeremie.com for more information
//	or email jer@jeremie.com with questions/suggestions.
//
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
////////// Simple XML Processing Library //////////////////////
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
////   Fully complies to the XML 1.0 spec
////   as a well-formed processor, with the
////   exception of full error reporting and
////   the document type declaration(and it's
////   related features, internal entities, etc).
///////////////////////////////////////////////////////////////



jive.xml.XMLParser = function(){

	var parser = null;

//	alert(XMLDom);
	if (window.ActiveXObject){

		var ARR_ACTIVEX = ["MSXML4.DOMDocument",
                   "MSXML3.DOMDocument",
                   "MSXML2.DOMDocument",
                   "MSXML.DOMDocument",
                   "Microsoft.XmlDom"]

            var xmlDoc = null;


            if(xmlDoc == null){
			var bFound = false;
			for(var i=0;i<ARR_ACTIVEX.length && !bFound;i++){
				try{
					xmlDoc=new ActiveXObject(ARR_ACTIVEX[i]);
					bFound = true;
				}catch(e){
				}
			}
		}

		if(xmlDoc == null){
			alert("No XML parser available");
			return;
		}
		parser = function(str){
			xmlDoc.async="false";
			xmlDoc.loadXML(str);
			return xmlDoc;
		}


	}

	if(parser == null && window.DOMParser){
		var xmlDoc = new DOMParser();

		parser = function(str){
			var doc = xmlDoc.parseFromString(str, "text/xml");
			var roottag = doc.documentElement;
			if ((roottag.tagName == "parserError") ||
			    (roottag.namespaceURI == "http://www.mozilla.org/newlayout/xml/parsererror.xml")){
				    return null;
			}
			return doc;
		}

	}else if (parser == null && document.implementation && document.implementation.createDocument){
		//create the DOM Document the standards way
		var xmlDoc = document.implementation.createDocument("","", null);
		parser = function(str){
			xmlDoc.async="false";
			xmlDoc.loadXML(str);
			return xmlDoc;
		}
	}else{
        parser = function(str){
            return jive.xml.Xparse(str);
        }
	}


	this.parse = function(str){
		if(parser != null){
			return parser(str);
		}else{
			throw "no xml parser defined"
		}
	}

}








// url is a string
// control is the controller
// rdyFun will be called when a successful result is returned from the server
// errFun will be called if anything goes wrong
jive.model.Ajax = function(control, rdyFun, errFun){
	var that = this;

	// parameters is a form
	this.POST = function (url, parameters) {

		var readyFunction = function(reply){
			try{
				// reply is the message from the server
				// check to see if the server sent back JSON
				var list = null;
                if(!$obj(list) || list == null || !$obj(list.documentElement) || list.documentElement == null){
                    var parser = new jive.xml.XMLParser();
                    try{
                        list = parser.parse(reply);
                    }catch(e){
                        errFun("XML Parse exception");
                        return;
                    }
                }
				// list is a ROOT xml object
				// and holds the actual document in the
				// .contents property
				if($obj(list) && list != null && $obj(list.documentElement) && list.documentElement != null){
					// if it returned results at all
					// then get them
					list = list.documentElement;
				}else{
					// otherwise its an error
					errFun("XML Parse exception");
					return;
				}

				if(list.tagName == "br"){
					// an exception
					//
					// we should log this somehow ?
					errFun("Server Exception");
				}else
				if(list.tagName == "NotLoggedInException"){
					control.handleLogIn(function(){
						that.POST(url, parameters);
					});
				}else{
					control.poke();
					rdyFun(list);
				}
			}catch(e){
				alert("ajax error:" + e);
			}
		};

		var errorFun = function(){
//			500 error
			errFun("500 Status");
		}

		var ajax = new jive.ext.y.yAjax(readyFunction, errorFun);
		ajax.POST(url, parameters);
	}
}


jive.model.Controller = function(){

    var that = this;

    //
    // fake default language for now
    //
    var def_lang = new Object();
    def_lang.childNodes = new Array();


    //
    // return an object to help us ajax
    this.newAjax = function(rdyFun, errFun){
        return new jive.model.Ajax(that, rdyFun, errFun);
    };

    //
    //
    // settings / etc managers here
    //
    var login_manager = new jive.model.LoginManager(that);

    this.getLoginManager = function(){
        return login_manager;
    }

    var settings_manager = new jive.model.SettingsManager(that);

    this.getSettingsManager = function(){
        return settings_manager;
    }

    var refresh_manager = new jive.model.RefreshManager(that);

    this.getRefreshManager = function(){
        return refresh_manager;
    }

    var language_manager = new jive.model.LanguageManager(that, default_lang);

    this.getLanguageManager = function(){
        return language_manager;
    }




    //
    //
    // model below here
    //

    var project_cache = new jive.model.ProjectCache(that);

    this.getProjectCache = function(){
        return project_cache;
    }

    var user_cache = new jive.model.UserCache(that);

    this.getUserCache = function(){
        return user_cache;
    }

    var task_cache = new jive.model.TaskCache(that);

    this.getTaskCache = function(){
        return task_cache;
    }


    //
    // we found out that we're logged out
    // when we tried to thunk(). log back
    // in, and thunk() again.
    this.handleLogIn = function(thunk){
        // uh oh, tell the refresh manager
        // that we're logged out

        that.getRefreshManager().loggedOut();

        //
        // optionally let the user login again via ajax
        // if successful, be sure to call thunk();

    }

    // an ajax call succeeded
    // tell the refresh manager
    this.poke = function(){
        that.getRefreshManager().poke();
    }

    //
    // return true if the calendar/project is
    // visible, false otherwise
    this.isCalendarVisibleHuh = function(id){
        return true;
    }

    this.isReadOnly = function(){
        return false;
    }




    /******************************************
     * listener functions
     ******************************************/
    this.addListener = function(list){
        listeners.push(list);
    }

    var listeners = new Array();
    var listener_actions = new Array();
    /**
     * act must be a thunk (a function without arguments)
     * it will be executed after either
     * notifyLoadFinish or notifyLoadFail
     */
    this.addListenerAction = function(act){
        listener_actions.push(act);
    }

    /**
     * private
     * executes all the listener actions
     */
    this.executeListenerActions = function(){
        while(listener_actions.length > 0){
            listener_actions[0]();
            listener_actions.splice(0,1);
        }
    }

    this.removeListener = function(list){
        for(var i=0;i<listeners.length;i++){
            if(listeners[i] == list){
                listeners.splice(i, 1);
            }
        }
    }

    /**
     * notification functions
     */
    this.notifyTinyMCELoaded = function(){
        for(var i=0;i<listeners.length;i++){
            listeners[i].tinyMCELoaded();
        }
        that.executeListenerActions();
    }
}

/**
 * checks if Date d1 is <= Date d2
 * by checking day of the month/year
 * only, (hours/min/sec/mil are ignored)
 *
 * ex
 * var d1 = new Date(); d1.setFullYear(2004);
 * var d2 = new Date(); d1.setFullYear(2005);
 * date(LTEQ(d1, d2)); // true
 * date(LTEQ(d2, d1)); // false
 */
jive.model.dateLTEQ = function(d1, d2){
	return (d1.getFullYear() < d2.getFullYear() || d1.getFullYear() == d2.getFullYear() && (
	        d1.getMonth() < d2.getMonth()  || d1.getMonth() == d2.getMonth() && (
	        d1.getDate() <= d2.getDate())));
}
jive.model.dateLT = function(d1, d2){
	return (d1.getFullYear() < d2.getFullYear() || d1.getFullYear() == d2.getFullYear() && (
	        d1.getMonth() < d2.getMonth()  || d1.getMonth() == d2.getMonth() && (
	        d1.getDate() < d2.getDate())));
}


jive.model.dateGT = function(d1, d2){
	return jive.model.dateLT(d2, d1);
}
jive.model.dateGTEQ = function(d1, d2){
	return jive.model.dateLTEQ(d2, d1);
}
/*
 * compares if two dates have equal month and year
 */
jive.model.monthYearEQ = function(d1, d2){
	return d1.getMonth() == d2.getMonth() &&
	        d1.getFullYear() == d2.getFullYear();
}
jive.model.dateEQ = function(d1, d2){
	return (d1.getDate() == d2.getDate() &&
	        d1.getMonth() == d2.getMonth() &&
	        d1.getFullYear() == d2.getFullYear());
}
/*
 * compares if two dates are equal (including time HH:MM only)
 */
jive.model.datetimeEQ = function(d1, d2){
	return (d1.getFullYear() == d2.getFullYear() &&
	        d1.getMonth() == d2.getMonth() &&
	        d1.getDate() == d2.getDate() &&
		d1.getHours() == d2.getHours() &&
		d1.getMinutes() == d2.getMinutes());
}
/*
 * compares if two dates are less than or equal (including time HH:MM only)
 */
jive.model.datetimeLTEQ = function(d1, d2){
	return (d1.getFullYear() < d2.getFullYear() || (d1.getFullYear() == d2.getFullYear() &&
	        (d1.getMonth() < d2.getMonth() || d1.getMonth() == d2.getMonth() &&
	        (d1.getDate() < d2.getDate() || d1.getDate() == d2.getDate() &&
		(d1.getHours() < d2.getHours() || d1.getHours() == d2.getHours() &&
		d1.getMinutes() <= d2.getMinutes())))));
}
/**
 * this subtracts one month from a date
 *
 * THIS FIXES A SAFARI BUG (OR 'FEATURE' :)
 *
 * this also fixes subtracting a month if the date >= 29
 * ie, if it's march 29th - 1 month = feb 29th = march 1st :(
 * instead, it'll subtract a month, and if the month is still the same
 * it'll subtract 1 day until it's different
 */
jive.model.dateMinusMonth = function(d){
	var m = d.getMonth();
	if(d.getMonth() == 0){
		d.setFullYear(d.getFullYear() - 1);
		d.setMonth(11);
	}else{
		d.setMonth(d.getMonth()-1);
	}
	while(d.getMonth() == m){
		d.setDate(d.getDate() - 1);
	}
}
/**
 * this subtracts one week from a date
 *
 * THIS FIXES A SAFARI BUG (OR 'FEATURE' :)
 */
jive.model.dateMinusWeek = function(d){
	d.setDate(d.getDate()-7);
}
/**
 * this subtracts one day from a date
 *
 * THIS FIXES A SAFARI BUG (OR 'FEATURE' :)
 */
jive.model.dateMinusDay = function(d){
	if(d.getDate() == 0 && d.getDate() == 1){
		d.setFullYear(d.getFullYear() - 1);
		d.setMonth(11);
		d.setDate(31);
	}else{
		d.setDate(d.getDate()-1);
	}
}


/**
 * helps format common date strings
 */
jive.model.DateHelper = function(control){

	var settings = control.getSettingsManager();
	var lm = control.getLanguageManager();
	var that = this;

//	var shortDayNames = new Array("Sun","Mon","Tue","Wed","Thu","Fri","Sat");
//	var dayNames = new Array("Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday");
//	var longMonth = new Array("January","February","March","April","May","June","July","August","September","October","November","December");
//	var shortMonth = new Array("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec");


	/**
	 * returns the human readable difference
	 * between 2 datetimes
	 * ie, 2 days, or about an hour
	 *
	 * it should be able to complete the sentance:
	 * the event starts _________
	 * "in 4 minutes"
	 * "15 minutes ago"
	 */
	this.readableDifference = function(d1, d2){
		var secs = d1.getTime() - d2.getTime();
		secs = secs / 1000;

		var ago = false;
		if(secs <= 0){
			ago = true;
			secs = -1 * secs;
		}

		var ret = "";

		if(secs < 10){
			return "just now";
		}else if(secs < 20){
			ret = "a few seconds";
		}else if(secs < 60){
			ret = "less than a minute";
		}else if(secs < 90){
			ret = "about a minute";
		}else{
			var mins = Math.ceil(secs / 60.0);
			if(mins <=50){
				var s = (mins == 1) ? "" : "s";
				ret = mins + " minute" + s;
			}else{
				var hours = Math.ceil(mins / 60.0);
				if(hours < 20){
					var s = (hours == 1) ? "" : "s";
					ret = hours + " hour" + s;
				}else{
					var days = Math.round(hours / 24.0);
					if(days < 7){
						var s = (days == 1) ? "" : "s";
						ret = days + " day" + s;
					}else{
						var weeks = Math.ceil(days / 7.0);
						var s = (weeks == 1) ? "" : "s";
						ret = weeks + " week" + s;
					}
				}
			}
		}

		if(ago){
			return ret + " ago";
		}else{
			return "in " + ret;
		}
	}


	/**
	 * returns the human readable difference
	 * between 2 datetimes
	 * ie, 2 days (in days/weeks, not hours/minutes)
	 *
	 * it should be able to complete the sentance:
	 * the event starts _________
	 * "in 4 days"
	 * "15 weeks ago"
	 */
	this.readableDateDifference = function(d11, d22){

		var d1 = new Date();
		d1.setTime(d11.getTime());
		d1.setHours(0);
		d1.setMinutes(0);
		d1.setSeconds(0);
		d1.setMilliseconds(0);

		var d2 = new Date();
		d2.setTime(d22.getTime());
		d2.setHours(0);
		d2.setMinutes(0);
		d2.setSeconds(0);
		d2.setMilliseconds(0);


		var secs = d1.getTime() - d2.getTime();
		secs = secs / 1000;

		var ago = false;
		if(jive.model.dateLT(d1, d2)){
			ago = true;
		}
		secs = Math.abs(secs);

		var ret = "";

			var mins = Math.ceil(secs / 60.0);
			var hours = Math.ceil(mins / 60.0);
			var days = Math.floor(hours / 24.0);
			if(days == 0){
				ret = "today";
			}else if(days == 1 && ago){
				ret = "yesterday";
			}else if(days == 1 && !ago){
				ret = "tomorrow";
			}else if(days < 7){
				var s = (days == 1) ? "" : "s";
				ret = days + " day" + s;
				if(ago){
					ret = ret + " ago";
				}else{
					ret = "in " + ret;
				}
			}else{
				var weeks = Math.ceil(days / 7.0);
				var s = (weeks == 1) ? "" : "s";
				ret = weeks + " week" + s;
				if(ago){
					ret = ret + " ago";
				}else{
					ret = "in " + ret;
				}
			}
		return ret;
	}

	/**
	 * returns time formatted
	 * yyyy-mm-dd hh:ii:ss
	 */
	this.formatToDateTime = function(d){
		var year = d.getFullYear();
		var month = d.getMonth() + 1;
		if(month < 10) month = "0" + month;
		var day = d.getDate();
		if(day < 10) day = "0" + day;

		var hour = d.getHours();
		if(hour < 10) hour = "0" + hour;
		var minute = d.getMinutes();
		if(minute < 10) minute = "0" + minute;
		var second = d.getSeconds();
		if(second < 10) second = "0" + second;

		return year + "-" + month + "-" + day + " " + hour + ":" + minute + ":" + second;
	}

	/**
	 * returns time formatted
	 * as [h]h:mma
	 */
	this.formatTo12HourTime = function(d){
		var format = settings.getTimeFormat();
		if(format == "3:00p"){
			var hour = d.getHours();
			var minute = d.getMinutes();
			if(minute < 10) minute = "0" + minute;
			var ampm = "a";

			if(hour >= 12){
				ampm = "p";
				hour -= 12;
			}
			if(hour == 0){
				hour = 12;
			}
			return hour + ":" + minute + ampm;
		}else{ // 15:00
			var hour = d.getHours();
			var minute = d.getMinutes();
			if(minute < 10) minute = "0" + minute;
			return hour + ":" + minute;
		}
	}

	/**
	 * returns time formatted
	 * as [h]ha
	 */
	this.formatToHourTime = function(d){
		var format = settings.getTimeFormat();
		if(format == "3:00p"){
			var hour = d.getHours();
			var ampm = "a";

			if(hour >= 12){
				ampm = "p";
				hour -= 12;
			}
			if(hour == 0){
				hour = 12;
			}
			return hour + ampm;
		}else{ // 15
			var hour = d.getHours();
			return hour;
		}
	}

	/**
	 * returns time formatted
	 * yyyy-mm-dd
	 */
	this.formatToStandardTime = function(d){
		var year = "" + d.getFullYear();
		var month = d.getMonth() + 1;
		if(month < 10) month = "0" + month;
		var day = d.getDate();
		if(day < 10) day = "0" + day;

		var hour = d.getHours();
		if(hour < 10) hour = "0" + hour;
		var minute = d.getMinutes();
		if(minute < 10) minute = "0" + minute;
		var second = d.getSeconds();
		if(second < 10) second = "0" + second;
		return year + "-" + month + "-" + day;
	}

	/**
	 * returns date formatted
	 * as mm/dd
	 */
	this.formatToShortDate = function(d){
		var month = d.getMonth()+1;
		var day = d.getDate();

		var format = settings.getDateFormat();
		if(format == "4/30"){
			return month + "/" + day;
		}else{ // 30/4
			return day + "/" + month;
		}
	}

	/**
	 * returns date formatted
	 * as Monday, February 14th, 2005
	 */
	this.formatToLongDate = function(d){
		var tlang = lm.getActiveLanguage();
		var str = tlang.longDay(d.getDay()) + ", ";

		var month = tlang.longMonth(d.getMonth());
		var day = d.getDate();


		var format = settings.getDateFormat();
		if(format == "4/30"){
			if(d.getDate() == 1 || d.getDate() == 21 || d.getDate() == 31){
				day += "st";
			}else
			if(d.getDate() == 2 || d.getDate() == 22){
				day += "nd";
			}else
			if(d.getDate() == 3 || d.getDate() == 23){
				day += "rd";
			}else{
				day += "th";
			}
			str += month + " " + day;
		}else{ // 30/4
			str += day + " " + month;
		}
		str += ", " + d.getFullYear();

		return str;
	}

	/**
	 * returns date formatted
	 * as Mon, Feb 14th, 2005
	 */
	this.formatToMediumDate = function(d){
		var str = that.formatToMediumDateNoYear(d);
		str += ", " + d.getFullYear();

		return str;
	}

	/**
	 * returns date formatted
	 * as Mon, Feb 14th
	 */
	this.formatToMediumDateNoYear = function(d){
		var tlang = lm.getActiveLanguage();
		var str = tlang.shortDay(d.getDay()) + ", ";

		var month = tlang.shortMonth(d.getMonth());
		var day = d.getDate();


		var format = settings.getDateFormat();
		if(format == "4/30"){
			if(d.getDate() == 1 || d.getDate() == 21 || d.getDate() == 31){
				day += "st";
			}else
			if(d.getDate() == 2 || d.getDate() == 22){
				day += "nd";
			}else
			if(d.getDate() == 3 || d.getDate() == 23){
				day += "rd";
			}else{
				day += "th";
			}
			str += month + " " + day;
		}else{ // 30/4
			str += day + " " + month;
		}

		return str;
	}

	/**
	 * returns date formatted
	 * as Feb 14th
	 */
	this.formatToMedDate = function(d){
		var tlang = lm.getActiveLanguage();

		var month = tlang.shortMonth(d.getMonth());
		var day = d.getDate();
		var str = "";
		var format = settings.getDateFormat();
		if(format == "4/30"){
			var sfx = "";
			if(d.getDate() == 1 || d.getDate() == 21 || d.getDate() == 31){
				sfx = "st";
			}else
			if(d.getDate() == 2 || d.getDate() == 22){
				sfx = "nd";
			}else
			if(d.getDate() == 3 || d.getDate() == 23){
				sfx = "rd";
			}else{
				sfx = "th";
			}
			str = month + " " + day + sfx;
		}else{
			str = day + " " + month;
		}

		return str;
	}

	/**
	 * returns date formatted
	 * as Febuary 14th
	 */
	this.formatToMedLongDate = function(d){
		var tlang = lm.getActiveLanguage();

		var month = tlang.longMonth(d.getMonth());
		var day = d.getDate();
		var str = "";
		var format = settings.getDateFormat();
		if(format == "4/30"){
			var sfx = "";
			if(d.getDate() == 1 || d.getDate() == 21 || d.getDate() == 31){
				sfx = "st";
			}else
			if(d.getDate() == 2 || d.getDate() == 22){
				sfx = "nd";
			}else
			if(d.getDate() == 3 || d.getDate() == 23){
				sfx = "rd";
			}else{
				sfx = "th";
			}
			str = month + " " + day + sfx;
		}else{
			str = day + " " + month;
		}

		return str;
	}

	this.getMonthName = function(d){
		var tlang = lm.getActiveLanguage();
		return tlang.longMonth(d.getMonth());
	}
}


var default_lang = {
"childNodes": [
	{
	"childNodes": [
		{
		"tagName": "lang_id",
		"childNodes": [{ "nodeValue": "1"
}]
		},
		{
		"tagName": "name",
		"childNodes": [{ "nodeValue": "English"
}]
		},
		{
		"childNodes": [
			{
			"tagName": "eng_name",
			"childNodes": [{ "nodeValue": "English"
}]
			},
			{
			"tagName": "name",
			"childNodes": [{ "nodeValue": "English"
}]
			},
			{
			"tagName": "january",
			"childNodes": [{ "nodeValue": "January"
}]
			},
			{
			"tagName": "february",
			"childNodes": [{ "nodeValue": "February"
}]
			},
			{
			"tagName": "march",
			"childNodes": [{ "nodeValue": "March"
}]
			},
			{
			"tagName": "april",
			"childNodes": [{ "nodeValue": "April"
}]
			},
			{
			"tagName": "may",
			"childNodes": [{ "nodeValue": "May"
}]
			},
			{
			"tagName": "june",
			"childNodes": [{ "nodeValue": "June"
}]
			},
			{
			"tagName": "july",
			"childNodes": [{ "nodeValue": "July"
}]
			},
			{
			"tagName": "august",
			"childNodes": [{ "nodeValue": "August"
}]
			},
			{
			"tagName": "september",
			"childNodes": [{ "nodeValue": "September"
}]
			},
			{
			"tagName": "october",
			"childNodes": [{ "nodeValue": "October"
}]
			},
			{
			"tagName": "november",
			"childNodes": [{ "nodeValue": "November"
}]
			},
			{
			"tagName": "december",
			"childNodes": [{ "nodeValue": "December"
}]
			},
			{
			"tagName": "sunday",
			"childNodes": [{ "nodeValue": "Sunday"
}]
			},
			{
			"tagName": "monday",
			"childNodes": [{ "nodeValue": "Monday"
}]
			},
			{
			"tagName": "tuesday",
			"childNodes": [{ "nodeValue": "Tuesday"
}]
			},
			{
			"tagName": "wednesday",
			"childNodes": [{ "nodeValue": "Wednesday"
}]
			},
			{
			"tagName": "thursday",
			"childNodes": [{ "nodeValue": "Thursday"
}]
			},
			{
			"tagName": "friday",
			"childNodes": [{ "nodeValue": "Friday"
}]
			},
			{
			"tagName": "saturday",
			"childNodes": [{ "nodeValue": "Saturday"
}]
			},
			{
			"tagName": "sh_january",
			"childNodes": [{ "nodeValue": "Jan"
}]
			},
			{
			"tagName": "sh_february",
			"childNodes": [{ "nodeValue": "Feb"
}]
			},
			{
			"tagName": "sh_march",
			"childNodes": [{ "nodeValue": "Mar"
}]
			},
			{
			"tagName": "sh_april",
			"childNodes": [{ "nodeValue": "Apr"
}]
			},
			{
			"tagName": "sh_may",
			"childNodes": [{ "nodeValue": "May"
}]
			},
			{
			"tagName": "sh_june",
			"childNodes": [{ "nodeValue": "Jun"
}]
			},
			{
			"tagName": "sh_july",
			"childNodes": [{ "nodeValue": "Jul"
}]
			},
			{
			"tagName": "sh_august",
			"childNodes": [{ "nodeValue": "Aug"
}]
			},
			{
			"tagName": "sh_september",
			"childNodes": [{ "nodeValue": "Sep"
}]
			},
			{
			"tagName": "sh_october",
			"childNodes": [{ "nodeValue": "Oct"
}]
			},
			{
			"tagName": "sh_november",
			"childNodes": [{ "nodeValue": "Nov"
}]
			},
			{
			"tagName": "sh_december",
			"childNodes": [{ "nodeValue": "Dec"
}]
			},
			{
			"tagName": "sh_sunday",
			"childNodes": [{ "nodeValue": "Sun"
}]
			},
			{
			"tagName": "sh_monday",
			"childNodes": [{ "nodeValue": "Mon"
}]
			},
			{
			"tagName": "sh_tuesday",
			"childNodes": [{ "nodeValue": "Tue"
}]
			},
			{
			"tagName": "sh_wednesday",
			"childNodes": [{ "nodeValue": "Wed"
}]
			},
			{
			"tagName": "sh_thursday",
			"childNodes": [{ "nodeValue": "Thu"
}]
			},
			{
			"tagName": "sh_friday",
			"childNodes": [{ "nodeValue": "Fri"
}]
			},
			{
			"tagName": "sh_saturday",
			"childNodes": [{ "nodeValue": "Sat"
}]
			},
			{
			"tagName": "loading",
			"childNodes": [{ "nodeValue": "Loading..."
}]
			},
			{
			"tagName": "sb_actions",
			"childNodes": [{ "nodeValue": "Actions"
}]
			},
			{
			"tagName": "sb_my_calendars",
			"childNodes": [{ "nodeValue": "My Calendars"
}]
			},
			{
			"tagName": "sb_other_calendars",
			"childNodes": [{ "nodeValue": "Other Calendars"
}]
			},
			{
			"tagName": "sb_alerts",
			"childNodes": [{ "nodeValue": "Alerts"
}]
			},
			{
			"tagName": "sb_general_tasks",
			"childNodes": [{ "nodeValue": "General Tasks"
}]
			},
			{
			"tagName": "sb_no_tasks",
			"childNodes": [{ "nodeValue": "no tasks"
}]
			},
			{
			"tagName": "sb_reminder",
			"childNodes": [{ "nodeValue": "Reminder"
}]
			},
			{
			"tagName": "sb_reminders",
			"childNodes": [{ "nodeValue": "Reminders"
}]
			},
			{
			"tagName": "sb_alert",
			"childNodes": [{ "nodeValue": "Alert"
}]
			},
			{
			"tagName": "motto",
			"childNodes": [{ "nodeValue": "Simply Spectacular Time Management"
}]
			},
			{
			"tagName": "nav_month",
			"childNodes": [{ "nodeValue": "month"
}]
			},
			{
			"tagName": "nav_week",
			"childNodes": [{ "nodeValue": "week"
}]
			},
			{
			"tagName": "nav_day",
			"childNodes": [{ "nodeValue": "day"
}]
			},
			{
			"tagName": "nav_today",
			"childNodes": [{ "nodeValue": "today"
}]
			},
			{
			"tagName": "nav_tomorrow",
			"childNodes": [{ "nodeValue": "tomorrow"
}]
			},
			{
			"tagName": "nav_refresh",
			"childNodes": [{ "nodeValue": "refresh"
}]
			},
			{
			"tagName": "nav_list",
			"childNodes": [{ "nodeValue": "list"
}]
			},
			{
			"tagName": "nav_overview",
			"childNodes": [{ "nodeValue": "overview"
}]
			},
			{
			"tagName": "nav_feedback",
			"childNodes": [{ "nodeValue": "feedback"
}]
			},
			{
			"tagName": "nav_send",
			"childNodes": [{ "nodeValue": "Send"
}]
			},
			{
			"tagName": "nav_settings",
			"childNodes": [{ "nodeValue": "settings"
}]
			},
			{
			"tagName": "nav_advanced",
			"childNodes": [{ "nodeValue": "advanced"
}]
			},
			{
			"tagName": "nav_logout",
			"childNodes": [{ "nodeValue": "logout"
}]
			},
			{
			"tagName": "nav_prelogout",
			"childNodes": [{ "nodeValue": "Any unsaved changes will be lost!"
}]
			},
			{
			"tagName": "nav_back",
			"childNodes": [{ "nodeValue": "Back to:"
}]
			},
			{
			"tagName": "nav_event",
			"childNodes": [{ "nodeValue": "event"
}]
			},
			{
			"tagName": "nav_task",
			"childNodes": [{ "nodeValue": "task"
}]
			},
			{
			"tagName": "nav_invite",
			"childNodes": [{ "nodeValue": "Invite!"
}]
			},
			{
			"tagName": "nav_save",
			"childNodes": [{ "nodeValue": "Save"
}]
			},
			{
			"tagName": "nav_filter",
			"childNodes": [{ "nodeValue": "filter"
}]
			},
			{
			"tagName": "feedback_title",
			"childNodes": [{ "nodeValue": "What do you think about Jotlet?"
}]
			},
			{
			"tagName": "feedback_name",
			"childNodes": [{ "nodeValue": "your name:"
}]
			},
			{
			"tagName": "feedback_body",
			"childNodes": [{ "nodeValue": "we welcome brutal honesty:"
}]
			},
			{
			"tagName": "feedback_error",
			"childNodes": [{ "nodeValue": "There was an error sending your feedback. Please try again in a few minutes."
}]
			},
			{
			"tagName": "feedback_thanks",
			"childNodes": [{ "nodeValue": "Thanks for your feedback!"
}]
			},
			{
			"tagName": "cal_create",
			"childNodes": [{ "nodeValue": "create a calendar"
}]
			},
			{
			"tagName": "cal_add",
			"childNodes": [{ "nodeValue": "Add Calendar"
}]
			},
			{
			"tagName": "cal_edit",
			"childNodes": [{ "nodeValue": "Edit Calendar"
}]
			},
			{
			"tagName": "cal_delete",
			"childNodes": [{ "nodeValue": "Delete Calendar"
}]
			},
			{
			"tagName": "cal_remove",
			"childNodes": [{ "nodeValue": "Remove Calendar"
}]
			},
			{
			"tagName": "cal_name",
			"childNodes": [{ "nodeValue": "calendar name"
}]
			},
			{
			"tagName": "cal_color",
			"childNodes": [{ "nodeValue": "select a color for this calendar:"
}]
			},
			{
			"tagName": "color_red",
			"childNodes": [{ "nodeValue": "red"
}]
			},
			{
			"tagName": "color_blue",
			"childNodes": [{ "nodeValue": "blue"
}]
			},
			{
			"tagName": "color_green",
			"childNodes": [{ "nodeValue": "green"
}]
			},
			{
			"tagName": "color_pink",
			"childNodes": [{ "nodeValue": "pink"
}]
			},
			{
			"tagName": "color_purple",
			"childNodes": [{ "nodeValue": "purple"
}]
			},
			{
			"tagName": "color_orange",
			"childNodes": [{ "nodeValue": "orange"
}]
			},
			{
			"tagName": "color_yellow",
			"childNodes": [{ "nodeValue": "yellow"
}]
			},
			{
			"tagName": "color_grey",
			"childNodes": [{ "nodeValue": "grey"
}]
			},
			{
			"tagName": "nav_close",
			"childNodes": [{ "nodeValue": "Close"
}]
			},
			{
			"tagName": "nav_cancel",
			"childNodes": [{ "nodeValue": "Cancel"
}]
			},
			{
			"tagName": "event_menu",
			"childNodes": [{ "nodeValue": "Add Event to..."
}]
			},
			{
			"tagName": "event_edit",
			"childNodes": [{ "nodeValue": "edit event"
}]
			},
			{
			"tagName": "event_edit_cap",
			"childNodes": [{ "nodeValue": "Edit Event"
}]
			},
			{
			"tagName": "event_loading",
			"childNodes": [{ "nodeValue": "Loading Add Event Page..."
}]
			},
			{
			"tagName": "event_add",
			"childNodes": [{ "nodeValue": "add event"
}]
			},
			{
			"tagName": "event_title",
			"childNodes": [{ "nodeValue": "event title"
}]
			},
			{
			"tagName": "event_dt",
			"childNodes": [{ "nodeValue": "date &amp; time"
}]
			},
			{
			"tagName": "event_begins",
			"childNodes": [{ "nodeValue": "begins"
}]
			},
			{
			"tagName": "event_ends",
			"childNodes": [{ "nodeValue": "ends"
}]
			},
			{
			"tagName": "event_at",
			"childNodes": [{ "nodeValue": "at"
}]
			},
			{
			"tagName": "event_allday",
			"childNodes": [{ "nodeValue": "This event is an all day event"
}]
			},
			{
			"tagName": "event_repeats",
			"childNodes": [{ "nodeValue": "This event repeats"
}]
			},
			{
			"tagName": "event_desc",
			"childNodes": [{ "nodeValue": "description"
}]
			},
			{
			"tagName": "event_add_cap",
			"childNodes": [{ "nodeValue": "Add Event"
}]
			},
			{
			"tagName": "event_update",
			"childNodes": [{ "nodeValue": "Update Event"
}]
			},
			{
			"tagName": "event_remind",
			"childNodes": [{ "nodeValue": "Add Reminder"
}]
			},
			{
			"tagName": "event_sb_delete",
			"childNodes": [{ "nodeValue": "Delete this event"
}]
			},
			{
			"tagName": "event_sb_delete_series",
			"childNodes": [{ "nodeValue": "Delete this series"
}]
			},
			{
			"tagName": "event_sb_edit",
			"childNodes": [{ "nodeValue": "Edit this event"
}]
			},
			{
			"tagName": "event_sb_export",
			"childNodes": [{ "nodeValue": "Download this event"
}]
			},
			{
			"tagName": "event_sb_perm",
			"childNodes": [{ "nodeValue": "This is a shared calendar. You do not have permission to edit or delete information."
}]
			},
			{
			"tagName": "task_menu",
			"childNodes": [{ "nodeValue": "Add Task to..."
}]
			},
			{
			"tagName": "task_edit",
			"childNodes": [{ "nodeValue": "edit task"
}]
			},
			{
			"tagName": "task_loading",
			"childNodes": [{ "nodeValue": "Loading Add Task Page..."
}]
			},
			{
			"tagName": "task_title",
			"childNodes": [{ "nodeValue": "task title"
}]
			},
			{
			"tagName": "task_due_date",
			"childNodes": [{ "nodeValue": "due date"
}]
			},
			{
			"tagName": "task_due",
			"childNodes": [{ "nodeValue": "due"
}]
			},
			{
			"tagName": "task_no_due",
			"childNodes": [{ "nodeValue": "This task does not have a due date"
}]
			},
			{
			"tagName": "task_repeats",
			"childNodes": [{ "nodeValue": "This task repeats"
}]
			},
			{
			"tagName": "task_add_cap",
			"childNodes": [{ "nodeValue": "Add Task"
}]
			},
			{
			"tagName": "task_update",
			"childNodes": [{ "nodeValue": "Update Task"
}]
			},
			{
			"tagName": "task_add",
			"childNodes": [{ "nodeValue": "add task"
}]
			},
			{
			"tagName": "task_sb_delete",
			"childNodes": [{ "nodeValue": "Delete this task"
}]
			},
			{
			"tagName": "task_sb_delete_series",
			"childNodes": [{ "nodeValue": "Delete this series"
}]
			},
			{
			"tagName": "task_sb_edit",
			"childNodes": [{ "nodeValue": "Edit this task"
}]
			},
			{
			"tagName": "task_sb_export",
			"childNodes": [{ "nodeValue": "Download this task"
}]
			},
			{
			"tagName": "task_sb_perm",
			"childNodes": [{ "nodeValue": "This is a shared calendar. You do not have permission to edit   or delete information."
}]
			},
			{
			"tagName": "info_in_cal",
			"childNodes": [{ "nodeValue": "in the %sub% calendar"
}]
			},
			{
			"tagName": "info_no_desc",
			"childNodes": [{ "nodeValue": "no description"
}]
			},
			{
			"tagName": "info_on",
			"childNodes": [{ "nodeValue": "on"
}]
			},
			{
			"tagName": "info_never",
			"childNodes": [{ "nodeValue": "never"
}]
			},
			{
			"tagName": "info_no_title",
			"childNodes": [{ "nodeValue": "no title"
}]
			},
			{
			"tagName": "info_more",
			"childNodes": [{ "nodeValue": "More Info"
}]
			},
			{
			"tagName": "info_allday",
			"childNodes": [{ "nodeValue": "All Day"
}]
			},
			{
			"tagName": "info_minutes",
			"childNodes": [{ "nodeValue": "minutes"
}]
			},
			{
			"tagName": "info_hours",
			"childNodes": [{ "nodeValue": "hours"
}]
			},
			{
			"tagName": "info_duration",
			"childNodes": [{ "nodeValue": "duration"
}]
			},
			{
			"tagName": "day_notes",
			"childNodes": [{ "nodeValue": "click to add daily notes"
}]
			},
			{
			"tagName": "day_tasks",
			"childNodes": [{ "nodeValue": "Tasks for the Day"
}]
			},
			{
			"tagName": "day_add_task",
			"childNodes": [{ "nodeValue": "add new task"
}]
			},
			{
			"tagName": "day_all_day",
			"childNodes": [{ "nodeValue": "All Day Events"
}]
			},
			{
			"tagName": "day_click",
			"childNodes": [{ "nodeValue": "click here to add some notes"
}]
			},
			{
			"tagName": "day_saving",
			"childNodes": [{ "nodeValue": "saving..."
}]
			},
			{
			"tagName": "day_loading",
			"childNodes": [{ "nodeValue": "loading..."
}]
			},
			{
			"tagName": "manage_cal",
			"childNodes": [{ "nodeValue": "Calendar Management"
}]
			},
			{
			"tagName": "manage_prop",
			"childNodes": [{ "nodeValue": "calendar properties"
}]
			},
			{
			"tagName": "manage_share",
			"childNodes": [{ "nodeValue": "share calendar"
}]
			},
			{
			"tagName": "manage_export",
			"childNodes": [{ "nodeValue": "export calendar"
}]
			},
			{
			"tagName": "manage_delete",
			"childNodes": [{ "nodeValue": "delete calendar: "
}]
			},
			{
			"tagName": "manage_remove",
			"childNodes": [{ "nodeValue": "remove calendar: "
}]
			},
			{
			"tagName": "manage_edit",
			"childNodes": [{ "nodeValue": "edit a calendar"
}]
			},
			{
			"tagName": "manage_select_buddies",
			"childNodes": [{ "nodeValue": "Select which buddies to share with from the list on the right"
}]
			},
			{
			"tagName": "manage_add_buddy",
			"childNodes": [{ "nodeValue": "Add Buddy"
}]
			},
			{
			"tagName": "manage_add_buddy_link",
			"childNodes": [{ "nodeValue": "Add a Buddy!"
}]
			},
			{
			"tagName": "manage_no_buddies",
			"childNodes": [{ "nodeValue": "No Buddies Here :("
}]
			},
			{
			"tagName": "manage_buddy_email",
			"childNodes": [{ "nodeValue": "enter buddy's email address"
}]
			},
			{
			"tagName": "manage_buddy_name",
			"childNodes": [{ "nodeValue": "Buddy Name:"
}]
			},
			{
			"tagName": "manage_invite_buddy",
			"childNodes": [{ "nodeValue": "Invite Your Buddy!"
}]
			},
			{
			"tagName": "manage_invite_buddy_to",
			"childNodes": [{ "nodeValue": "Invite %sub% to Jotlet!"
}]
			},
			{
			"tagName": "manage_go_back",
			"childNodes": [{ "nodeValue": "Go Back"
}]
			},
			{
			"tagName": "manage_share_bang",
			"childNodes": [{ "nodeValue": "Share!"
}]
			},
			{
			"tagName": "manage_share_self",
			"childNodes": [{ "nodeValue": "You can't share with yourself! :)"
}]
			},
			{
			"tagName": "manage_buddy_in_list",
			"childNodes": [{ "nodeValue": "%sub% is already in your buddy list"
}]
			},
			{
			"tagName": "manage_valid_email",
			"childNodes": [{ "nodeValue": "Please enter a valid email address"
}]
			},
			{
			"tagName": "manage_searching",
			"childNodes": [{ "nodeValue": "Searching..."
}]
			},
			{
			"tagName": "manage_adding_buddy",
			"childNodes": [{ "nodeValue": "Adding Buddy..."
}]
			},
			{
			"tagName": "manage_done_sharing",
			"childNodes": [{ "nodeValue": "Done Sharing!"
}]
			},
			{
			"tagName": "manage_error",
			"childNodes": [{ "nodeValue": "There was an error trying to share"
}]
			},
			{
			"tagName": "manage_sharing",
			"childNodes": [{ "nodeValue": "Sharing with Buddy..."
}]
			},
			{
			"tagName": "manage_add_fail",
			"childNodes": [{ "nodeValue": "could not add buddy :("
}]
			},
			{
			"tagName": "manage_inviting",
			"childNodes": [{ "nodeValue": "Inviting Buddy..."
}]
			},
			{
			"tagName": "manage_done_inviting",
			"childNodes": [{ "nodeValue": "Done Inviting!"
}]
			},
			{
			"tagName": "manage_fail_inviting",
			"childNodes": [{ "nodeValue": "Error Inviting"
}]
			},
			{
			"tagName": "manage_done_invite_msg",
			"childNodes": [{ "nodeValue": "We'll let you know when your buddy has accepted the invitation to Jotlet."
}]
			},
			{
			"tagName": "manage_fail_invite_msg",
			"childNodes": [{ "nodeValue": "There was an error inviting your buddy. Please try again in a few minutes."
}]
			},
			{
			"tagName": "manage_subscribe",
			"childNodes": [{ "nodeValue": "Subscribe"
}]
			},
			{
			"tagName": "manage_export_bang",
			"childNodes": [{ "nodeValue": "Export"
}]
			},
			{
			"tagName": "confirm_del_cal",
			"childNodes": [{ "nodeValue": "Delete calendar \"%sub%\"?\nThis cannot be undone!"
}]
			},
			{
			"tagName": "confirm_rem_cal",
			"childNodes": [{ "nodeValue": "Remove calendar \"%sub%\"?\nThis cannot be undone!"
}]
			},
			{
			"tagName": "confirm_del_event",
			"childNodes": [{ "nodeValue": "Delete event \"%sub%\"?\nThis cannot be undone!"
}]
			},
			{
			"tagName": "confirm_del_event_s",
			"childNodes": [{ "nodeValue": "Delete event series containing \"%sub%\"?\nThis cannot be undone!"
}]
			},
			{
			"tagName": "confirm_del_task",
			"childNodes": [{ "nodeValue": "Delete task \"%sub%\"?\nThis cannot be undone!"
}]
			},
			{
			"tagName": "confirm_del_task_s",
			"childNodes": [{ "nodeValue": "Delete task series containing \"%sub%\"?\nThis cannot be undone!"
}]
			},
			{
			"tagName": "setting_title",
			"childNodes": [{ "nodeValue": "Personal Settings"
}]
			},
			{
			"tagName": "setting_first",
			"childNodes": [{ "nodeValue": "First Name"
}]
			},
			{
			"tagName": "setting_last",
			"childNodes": [{ "nodeValue": "Last Name"
}]
			},
			{
			"tagName": "setting_email",
			"childNodes": [{ "nodeValue": "Email"
}]
			},
			{
			"tagName": "setting_sms",
			"childNodes": [{ "nodeValue": "SMS"
}]
			},
			{
			"tagName": "setting_zip",
			"childNodes": [{ "nodeValue": "Postal Code"
}]
			},
			{
			"tagName": "setting_language",
			"childNodes": [{ "nodeValue": "Language"
}]
			},
			{
			"tagName": "setting_save",
			"childNodes": [{ "nodeValue": "Save Profile"
}]
			},
			{
			"tagName": "setting_old_pass",
			"childNodes": [{ "nodeValue": "Old Password"
}]
			},
			{
			"tagName": "setting_new_pass",
			"childNodes": [{ "nodeValue": "New Password"
}]
			},
			{
			"tagName": "setting_confirm",
			"childNodes": [{ "nodeValue": "Confirm"
}]
			},
			{
			"tagName": "setting_save_pass",
			"childNodes": [{ "nodeValue": "Update Password"
}]
			},
			{
			"tagName": "setting_zone",
			"childNodes": [{ "nodeValue": "Time zone"
}]
			},
			{
			"tagName": "setting_zone_desc",
			"childNodes": [{ "nodeValue": "Time zones are listed with an * if they respect Daylight Savings Time"
}]
			},
			{
			"tagName": "setting_save_zone",
			"childNodes": [{ "nodeValue": "Save Local Settings"
}]
			},
			{
			"tagName": "setting_zoom",
			"childNodes": [{ "nodeValue": "Zoom Level"
}]
			},
			{
			"tagName": "setting_zoom_desc",
			"childNodes": [{ "nodeValue": "The zoom level affects how much time each row in day view represents. The 1 hour zoom will show a compact view, while the 15 minute zoom will show more detail."
}]
			},
			{
			"tagName": "setting_zoom_15_min",
			"childNodes": [{ "nodeValue": "15 minutes"
}]
			},
			{
			"tagName": "setting_zoom_30_min",
			"childNodes": [{ "nodeValue": "30 minutes"
}]
			},
			{
			"tagName": "setting_zoom_1_hour",
			"childNodes": [{ "nodeValue": "1 hour"
}]
			},
			{
			"tagName": "setting_save_zoom",
			"childNodes": [{ "nodeValue": "Save Zoom"
}]
			},
			{
			"tagName": "setting_shade",
			"childNodes": [{ "nodeValue": "Smart-Shading day cells"
}]
			},
			{
			"tagName": "setting_shade_desc",
			"childNodes": [{ "nodeValue": "Smart-Shading will automatically darken the background of busier days in month view. The busier the day, the darker the cell."
}]
			},
			{
			"tagName": "setting_save_shade",
			"childNodes": [{ "nodeValue": "Save Month Settings"
}]
			},
			{
			"tagName": "setting_profile",
			"childNodes": [{ "nodeValue": "user profile"
}]
			},
			{
			"tagName": "setting_change_pass",
			"childNodes": [{ "nodeValue": "change password"
}]
			},
			{
			"tagName": "setting_time_zones",
			"childNodes": [{ "nodeValue": "time zones"
}]
			},
			{
			"tagName": "setting_day_view",
			"childNodes": [{ "nodeValue": "day view"
}]
			},
			{
			"tagName": "setting_month_view",
			"childNodes": [{ "nodeValue": "month view"
}]
			},
			{
			"tagName": "setting_start_week",
			"childNodes": [{ "nodeValue": "Start of Week:  "
}]
			},
			{
			"tagName": "err_cal_name",
			"childNodes": [{ "nodeValue": "Please enter a name for the calendar"
}]
			},
			{
			"tagName": "email_invite",
			"childNodes": [{ "nodeValue": "Hey!\n\nCheck out Jotlet Calendar at www.jotlet.net! It's a great looking and easy to use online calendar.\n\nSign up so I can share my schedule with you!"
}]
			},
			{
			"tagName": "err_task_title",
			"childNodes": [{ "nodeValue": "Please enter a title for your task."
}]
			},
			{
			"tagName": "err_event_title",
			"childNodes": [{ "nodeValue": "Please enter a title for your event."
}]
			},
			{
			"tagName": "remind_adding",
			"childNodes": [{ "nodeValue": "Adding Reminder..."
}]
			},
			{
			"tagName": "remind_loading",
			"childNodes": [{ "nodeValue": "Loading Reminder..."
}]
			},
			{
			"tagName": "remind_email",
			"childNodes": [{ "nodeValue": "Email"
}]
			},
			{
			"tagName": "remind_sms",
			"childNodes": [{ "nodeValue": "Text Message"
}]
			},
			{
			"tagName": "remind_both",
			"childNodes": [{ "nodeValue": "Both"
}]
			},
			{
			"tagName": "remind_5_min",
			"childNodes": [{ "nodeValue": "5 minutes"
}]
			},
			{
			"tagName": "remind_4_hour",
			"childNodes": [{ "nodeValue": "4 hours"
}]
			},
			{
			"tagName": "remind_0_day",
			"childNodes": [{ "nodeValue": "the same day"
}]
			},
			{
			"tagName": "remind_1_day",
			"childNodes": [{ "nodeValue": "the day before"
}]
			},
			{
			"tagName": "remind_2_day",
			"childNodes": [{ "nodeValue": "2 days before"
}]
			},
			{
			"tagName": "remind_3_day",
			"childNodes": [{ "nodeValue": "3 days before"
}]
			},
			{
			"tagName": "remind_4_day",
			"childNodes": [{ "nodeValue": "4 days before"
}]
			},
			{
			"tagName": "remind_5_day",
			"childNodes": [{ "nodeValue": "5 days before"
}]
			},
			{
			"tagName": "remind_1_week",
			"childNodes": [{ "nodeValue": "1 week before"
}]
			},
			{
			"tagName": "remind_2_week",
			"childNodes": [{ "nodeValue": "2 weeks before"
}]
			},
			{
			"tagName": "remind_event",
			"childNodes": [{ "nodeValue": "%email% me %time% before this event"
}]
			},
			{
			"tagName": "remind_task_due",
			"childNodes": [{ "nodeValue": "%email% me at %time%%date% its due"
}]
			},
			{
			"tagName": "remind_task_no",
			"childNodes": [{ "nodeValue": "%email% me at %time% on %date%"
}]
			},
			{
			"tagName": "recur_daily",
			"childNodes": [{ "nodeValue": "daily"
}]
			},
			{
			"tagName": "recur_daily_num",
			"childNodes": [{ "nodeValue": "every %num% days"
}]
			},
			{
			"tagName": "recur_daily_weekday",
			"childNodes": [{ "nodeValue": "every weekday"
}]
			},
			{
			"tagName": "recur_weekly",
			"childNodes": [{ "nodeValue": "weekly"
}]
			},
			{
			"tagName": "recur_weekly_num",
			"childNodes": [{ "nodeValue": "every %num% weeks on:"
}]
			},
			{
			"tagName": "recur_monthly",
			"childNodes": [{ "nodeValue": "monthly"
}]
			},
			{
			"tagName": "recur_monthly_num",
			"childNodes": [{ "nodeValue": "day %num% of every %num2% months"
}]
			},
			{
			"tagName": "recur_monthly_date",
			"childNodes": [{ "nodeValue": "the %first% %weekday% of every %num% months"
}]
			},
			{
			"tagName": "recur_yearly",
			"childNodes": [{ "nodeValue": "yearly"
}]
			},
			{
			"tagName": "recur_yearly_exact",
			"childNodes": [{ "nodeValue": "every %month% %day%"
}]
			},
			{
			"tagName": "recur_yearly_rel",
			"childNodes": [{ "nodeValue": "the %first% %weekday% of %month%"
}]
			},
			{
			"tagName": "recur_custom",
			"childNodes": [{ "nodeValue": "custom"
}]
			},
			{
			"tagName": "recur_custom_desc",
			"childNodes": [{ "nodeValue": "Select your custom series of dates from the small calendar on the left."
}]
			},
			{
			"tagName": "recur_custom_dates",
			"childNodes": [{ "nodeValue": "Selected Dates: "
}]
			},
			{
			"tagName": "recur_end",
			"childNodes": [{ "nodeValue": "End Series:"
}]
			},
			{
			"tagName": "recur_end_after_e",
			"childNodes": [{ "nodeValue": "End after %num% events"
}]
			},
			{
			"tagName": "recur_end_after_t",
			"childNodes": [{ "nodeValue": "End after %num% tasks"
}]
			},
			{
			"tagName": "recur_end_by",
			"childNodes": [{ "nodeValue": "by %date%"
}]
			},
			{
			"tagName": "recur_event",
			"childNodes": [{ "nodeValue": "Repeat this event: "
}]
			},
			{
			"tagName": "recur_task",
			"childNodes": [{ "nodeValue": "Repeat this task: "
}]
			},
			{
			"tagName": "recur_first",
			"childNodes": [{ "nodeValue": "first"
}]
			},
			{
			"tagName": "recur_second",
			"childNodes": [{ "nodeValue": "second"
}]
			},
			{
			"tagName": "recur_third",
			"childNodes": [{ "nodeValue": "third"
}]
			},
			{
			"tagName": "recur_fourth",
			"childNodes": [{ "nodeValue": "fourth"
}]
			},
			{
			"tagName": "recur_fifth",
			"childNodes": [{ "nodeValue": "fifth"
}]
			},
			{
			"tagName": "recur_last",
			"childNodes": [{ "nodeValue": "last"
}]
			}
		],
		"tagName": "lang_table"
		}
	],
	"tagName": "language"
	}
],
"tagName": "languages"
};

jive.model.isLanguage = function(lang){
	return $def(lang) && $obj(lang) && lang != null && $def(lang.translate) && $def(lang.getId);
}

/**
 * represents a single language
 */
jive.model.Language = function(id, name, hash){

	var that = this;

	this.getId = function(){
		return id;
	}

	this.getName = function(){
		return name;
	}

	this.translate = function(key){
		var val = hash.get(key);
		if(val == false){
			alert("Language Exception: key \"" + key + "\" not found");
		}else{
			if(val == "_"){
				return " ";
			}else{
				val = val.replace(/#xD#xA/g,"\r\n");
				return val;
			}
		}
	}

    var colors = new Array();
    colors.push("red"),
    colors.push("blue"),
    colors.push("green"),
    colors.push("pink"),
    colors.push("purple"),
    colors.push("orange"),
    colors.push("yellow"),
    colors.push("grey");
	this.color = function(i){
		return that.translate("color_" + colors[i]);
	}

	this.longMonth = function(i){
		var names = new Array();
		names.push("january");
		names.push("february");
		names.push("march");
		names.push("april");
		names.push("may");
		names.push("june");
		names.push("july");
		names.push("august");
		names.push("september");
		names.push("october");
		names.push("november");
		names.push("december");
		return that.translate(names[i]);
	}

	this.shortMonth = function(i){
		var names = new Array();
		names.push("sh_january");
		names.push("sh_february");
		names.push("sh_march");
		names.push("sh_april");
		names.push("sh_may");
		names.push("sh_june");
		names.push("sh_july");
		names.push("sh_august");
		names.push("sh_september");
		names.push("sh_october");
		names.push("sh_november");
		names.push("sh_december");
		return that.translate(names[i]);
	}

	this.longDay = function(i){
		var names = new Array();
		names.push("sunday");
		names.push("monday");
		names.push("tuesday");
		names.push("wednesday");
		names.push("thursday");
		names.push("friday");
		names.push("saturday");
		return that.translate(names[i]);
	}

	this.shortDay = function(i){
		var names = new Array();
		names.push("sh_sunday");
		names.push("sh_monday");
		names.push("sh_tuesday");
		names.push("sh_wednesday");
		names.push("sh_thursday");
		names.push("sh_friday");
		names.push("sh_saturday");
		return that.translate(names[i]);
	}

	this.weekNumber = function(i){
		var names = new Array();
		names.push("recur_first");
		names.push("recur_second");
		names.push("recur_third");
		names.push("recur_fourth");
		names.push("recur_fifth");
		names.push("recur_last");
		return that.translate(names[i]);
	}
}


// checks for the existance of var default_lang
//
// if present, it parses it and stores it as the active language



/**
 * caches all day's comments objects that might show up in main views
 * (especially day view)
 */
jive.model.LanguageManager = function(control, default_lang){

	/**
	 * to reference other functions in this object
	 * from functions in this object,
	 * use that.func() sytax
	 */
	var that = this;

	/**
	 * the currently active language
	 * this will be what jotlet uses to translate the UIs
	 */
	var active_lang = null;


	/**
	 * the list of allowed languages
	 */
	var language_list = new Array();

	/**
	 * the cache of buddy objects
	 */
	var cache = new jive.ext.y.HashTable();


	/**
	 * the array of CommentCacheListener objects
	 *
	 * the object must have the following functions
	 *
	 * beginLoadingComments() // called when loading begins
	 * loadingCommentsFailed() // called when loading fails
	 * loadComment(comment) // the event parameter has been [re]loaded
	 * doneLoadingComment() // called when all calendars have been loaded
	 */
	var listeners = new Array();

	/**
	 * returns the active language
	 */
	this.getActiveLanguage = function(){
		return active_lang;
	}

	/**
	 * sets the active language
	 */
	this.setActiveLanguage = function(lang){
		if(jive.model.isLanguage(lang)){
			active_lang = lang;
			that.notifyLanguageChanged(active_lang);
		}else{
			return false;
		}
	}

	/**
	 * get languages that are loaded in the cache
	 */
	this.getLanguageList = function(){
		return cache.toArray();
	}

	/**
	 * get the available languages
	 */
	this.getSilentLanguages = function(){
		return language_list;
	}

	/**
	 * load buddies from DB and save in cache
	 */
	this.loadLanguage = function(lang){
		that.notifyLoadBegin();
		var a = control.newAjax(that.loadOk,that.loadFail);
		a.POST(HOSTURL + AJAXPATH + "?load_language","lang_id=" + lang);
	}

	/**
	 * save the active language
	 * and load it as active if it's in the cache,
	 * otherwise parse it
	 */
	this.saveLanguage = function(lang_id){
		that.notifyLoadBegin();
		// default load function will parse the language
		// and set it as active
		var okfun = function(lang_id){ return function(list){
			that.loadOk(list);
			var lang = cache.get(lang_id);
			if($obj(lang) && lang != null){
				that.setActiveLanguage(lang);
			}
		}}(lang_id);
		// if it's already parsed though, then just load it
		// from the cache
		var lang = cache.get(lang_id);
		if($obj(lang) && lang != null){
			okfun = function (lang_id){ return function(list){
				that.setActiveLanguage(cache.get(lang_id));
			}}(lang_id);
		}
		var a = control.newAjax(okfun, that.loadFail);
		a.POST(HOSTURL + AJAXPATH + "?save_language","lang_id=" + lang_id);
	}

	function parse(list){
		// reply is the message from the server
		// it should contain info about the calendars
		var tlang;

		if(list.childNodes.length > 0){
			if(list.childNodes[0].tagName == "language"){
				// it's a new language
				if(list.childNodes[0].childNodes.length > 0){
					lang = list.childNodes[0];
					var name = "";
					var id = 0;
					var hash = new jive.ext.y.HashTable();
					for(var j=0;j<lang.childNodes.length;j++){
						if(lang.childNodes[j].tagName == "name"){
							name = lang.childNodes[j].childNodes[0].nodeValue;
						}else
						if(lang.childNodes[j].tagName == "lang_id"){
							id = lang.childNodes[j].childNodes[0].nodeValue;
						}else
						if(lang.childNodes[j].tagName == "lang_table"){
							var table = lang.childNodes[j];
							for(var k=0;k<table.childNodes.length;k++){
								hash.put(table.childNodes[k].tagName, table.childNodes[k].childNodes[0].nodeValue);
							}
						}
					}
					lang = new jive.model.Language(id, name, hash);
					cache.put(lang.getId(), lang);
				}else{
					// fail, we loaded a blank language
					return false;
				}
			}else{
				return false;
			}
		}else{
			return false;
		}
		return lang;
	}


	/**
	 * loading buddies is successful
	 */
	this.loadOk = function(list){
		if(!parse(list)){
			that.notifyLoadFail();
		}else{
			that.notifyLoadFinish();
		}
	}

	/**
	 * loading buddies failed
	 */
	this.loadFail = function(){
		// notify listeners that the loading failed
		that.notifyLoadFail();
	}


	/******************************************
	 * listener functions
	 ******************************************/
	this.addListener = function(list){
		listeners.push(list);
	}

	this.removeListener = function(list){
		for(var i=0;i<listeners.length;i++){
			if(listeners[i] == list){
				listeners.splice(i, 1);
			}
		}
	}

	this.notifyLoadBegin = function(){
		for(var i=0;i<listeners.length;i++){
			listeners[i].beginLoadingLanguages();
		}
	}

	this.notifyLoad = function(lang){
		for(var i=0;i<listeners.length;i++){
			listeners[i].loadLanguage(lang);
		}
	}

	this.notifyLoadFinish = function(){
		for(var i=0;i<listeners.length;i++){
			listeners[i].doneLoadingLanguages();
		}
	}

	this.notifyLoadFail = function(){
		for(var i=0;i<listeners.length;i++){
			listeners[i].loadingLanguagesFailed();
		}
	}

	this.notifyLanguageChanged = function(lang){
		for(var i=0;i<listeners.length;i++){
			listeners[i].languageChanged(lang);
		}
	}



	try{
		/**
		 * we're checking to see if a language variable is
		 * already defined for us
		 * this way we don't have to ajax in the language
		 */
		if($def(default_lang)){
			var lang = parse(default_lang);
			if($obj(lang) && lang != null){
				that.setActiveLanguage(lang);
			}else{
				alert("error parsing");
			}
		}else{
			alert("no default langauge");
		}
	}catch(e){
		alert("language: " + e);
	}

    
//	try{
//		/**
//		 * get the list of default languages
//		 * this variable is defined at run time,
//		 * so we don't have to load in the list of languages
//		 * by ajax
//		 */
//		if($def(lang_list) && $def(lang_list[1]) && $def(lang_list.length)){
//			language_list = lang_list;
//		}else{
//			alert("empty language list");
//		}
//	}catch(e){
//		alert("language: " + e);
//	}

}




jive.model.LoginManager = function(control){

	var that = this;

	var listeners = new Array();

	/**
	 * if listeners want to modify themselves
	 * (ie, take itself out of the list)
	 * then they have to add a listener action
	 * which will be executed after all listeners
	 * have been notified of all events
	 * ie, these will only be executed after
	 * notifyLoadFinish and notifyLoadFail
	 */
	var listener_actions = new Array();

	function loginOk(reply){
		try{
			// reply is the message from the server
			// it should contain info about the calendars
			var parser = new jive.xml.XMLParser();
			var list = parser.parse(reply);
			// list is a ROOT xml object
			// and holds the actual document in the
			// documentElement attribute
			if($obj(list.documentElement) && list.documentElement != null){
				// if it returned results at all
				// then get them
				list = list.documentElement;
			}else{
				// otherwise show empty results
				list = new Object();
				list.childNodes = new Array();
				list.tagName = "failed";
			}
			if(list.tagName == "success"){
				that.notifyLoginOk();
			}else{
				that.notifyLoginFail();
			}
		}catch(e){
			alert(e);
		}
	}

	function loginFail(){
		that.notifyLoginFail();
	}

	this.login = function(password){
		var a = new jotlet.external.y.yAjax(loginOk, loginFail);
        alert("logging in via ajax");
//        a.POST(HOSTURL + AJAXPATH + "?login","username=" + control.getSettingsManager().getUserName() + "&password=" + password);
	}

	/******************************************
	 * listener functions
	 ******************************************/
	var lock = false;

	function executeListenerActions(){
		while(listener_actions.length > 0){
			listener_actions[0]();
			listener_actions.splice(0,1);
		}
	}

	this.addListener = function(list){
		if(!lock){
			listeners.push(list);
		}else{
			listener_actions.push(function(){that.addListener(list);});
		}
	}

	/**
	 * removes a listener
	 */
	this.removeListener = function(list){
		if(!lock){
			for(var i=0;i<listeners.length;i++){
				if(listeners[i] == list){
					listeners.splice(i, 1);
				}
			}
		}else{
			listener_actions.push(function(){that.removeListener(list);});
		}
	}


	/**
	 * notify listeners that the user is logged in
	 */
	this.notifyLoginOk = function(){
		lock = true;
		for(var i=0;i<listeners.length;i++){
			listeners[i].loginOk();
		}
		lock = false;
		executeListenerActions();
	}

	/**
	 * notify listeners that the user is logged in
	 */
	this.notifyLoginFail = function(){
		lock = true;
		for(var i=0;i<listeners.length;i++){
			listeners[i].loginFail();
		}
		lock = false;
		executeListenerActions();
	}
	/******************************************
	 * end listener functions
	 ******************************************/
}

jive.model.RefreshManager = function(control){

	var that = this;

	// listeners who want to know
	// what' we're doing
	var listeners = new Array();

	//
	// we need to keep track of the last time we
	// *know* that we were logged in
	// so that if we get logged out, we can
	// refresh all things that've been changed
	// since the last time we were logged in.
	//
	var last_session_date = control.getSettingsManager().getGMT();

	// lets keep track of the last time that we successfully
	// refreshed everything.
	var last_refresh = control.getSettingsManager().getGMT();

	// this tells whether we need to update last_login_date
	// when we're poked.
	//
	// we should not track login times if we have just been
	// asked to login again.
	//
	// once we know that we're logged out. we shouldn't track
	// login times until we've refreshed using the
	// refresh manager (which gets all changes since the
	// last time we *know* we're logged in).
	var tracking_session = true;

	// this is called after every successful ajax attempt
	// where we were logged in.
	//
	// if we had to re-login for the ajax to succeed,
	// then this is not called.
	//
	// this way, after every ajax call, we know the last
	// time that we were definately logged in.
	this.poke = function(){
		try{
			if(tracking_session){
				last_session_date = control.getSettingsManager().getGMT();
			}
		}catch(e){
			alert("refresh error:" + e);
		}
	}


	// the controller is letting us know that we're logged out
	// now all ajax calls that succeed will be after we've re-logged in
	this.loggedOut = function(){
		tracking_session = false;
	}



	function sendAjaxRefresh(dt, allHuh){
		try{
			that.notifyRefreshing();
			// subtract 5 seconds,
			// so that if anything, we sync more than
			// we need to
			dt.setTime(dt.getTime() - 5);
			var dh = new jive.model.DateHelper(control);
			var datestr = dh.formatToDateTime(dt);
			var a = control.newAjax(loadXML, loadFail);

			var min = dh.formatToDateTime(control.getEventCache().getMinTime());
			var max = dh.formatToDateTime(control.getEventCache().getMaxTime());

//			alert("min: " + min);

			a.POST(HOSTURL + AJAXPATH + "?refresh","dt=" + encodeURIComponent(datestr) + "&mindt=" + min + "&maxdt=" + max + (allHuh ? "&all" : ""));
		}catch(e){
			alert("refreshing: " + e);
		}
	}


	this.refresh = function(){
		// refresh from last_refresh
		// but only if we think we're logged in.
		//
		// there's no use in refreshing if we're
		// not logged in
		if(tracking_session){
			// normal login code here.
			sendAjaxRefresh(last_refresh, false);
		}
	}



	/**
	 * load in some refresh xml from somewhere else
	 */
	this.reload = function(list){
		var last_refresh_temp = last_refresh;
		that.notifyRefreshing();
		loadXML(list);
		last_refresh = last_refresh_temp;
	}


	var caches = new jive.ext.y.HashTable();
	this.getCustomCache = function(name){
		var c = caches.get(name);
		if(!$obj(c)){
			c = new jive.ext.y.HashTable();
			caches.put(name, c);
		}
		return c;
	}

	function resetCache(name){
		var c = new jive.ext.y.HashTable()
		caches.put(name, c);
		return c;
	}



	function loadEventCacheXML(list){
		for(var i=0;i<list.childNodes.length;i++){
			var c = resetCache(list.childNodes[i].tagName);
			for(var j=0;j<list.childNodes[i].childNodes.length;j++){
				var id = parseInt(list.childNodes[i].childNodes[j].childNodes[0].nodeValue);
				c.put(id, true);
			}
		}
	}


	/**
	 * loads an xml reply
	 *
	 * this will parse out the various responses,
	 * and send them to the approprate objects for
	 * continued refreshing.
	 *
	 * ie, the response will contain a
	 * <calendars></calendars> node
	 * and we'll send this node to the
	 * calendar manager, which will handle
	 * actually refreshing the calendar objects
	 * and notifying everyone of the change
	 */
	function loadXML(list){
		try{
			// now that we've refreshed, we can
			// refresh normally again
			tracking_session = true;


			for(var i=0;i<list.childNodes.length;i++){
				if(list.childNodes[i].tagName == "projects"){
					if(list.childNodes[i].childNodes.length > 0){
						control.getProjectCache().loadExternalProjects(list.childNodes[i]);
					}
				}else if(list.childNodes[i].tagName == "events"){
					if(list.childNodes[i].childNodes.length > 0){
						control.getEventCache().reloadEvents(list.childNodes[i]);
					}
				}else if(list.childNodes[i].tagName == "del_cals"){
					//
					// these are calendars that have been deleted
					//
					try{
						if(list.childNodes[i].childNodes.length > 0){
							control.getCalendarCache().unloadCalendars(list.childNodes[i]);
						}
					}catch(e){
						alert("error unloading calendars: " + e);
					}
				}else if(list.childNodes[i].tagName == "del_events"){
					//
					// these are events that have been deleted
					//
					try{
						if(list.childNodes[i].childNodes.length > 0){
							control.getEventCache().unloadEvents(list.childNodes[i]);
						}
					}catch(e){
						alert("error unloading calendars: " + e);
					}
				}else if(list.childNodes[i].tagName == "event_cache"){
					//
					// these are event caches
					//
					try{
						if(list.childNodes[i].childNodes.length > 0){
							loadEventCacheXML(list.childNodes[i]);
						}
					}catch(e){
						alert("error unloading calendars: " + e);
					}
				}else if(list.childNodes[i].tagName == "reminders"){
					if(list.childNodes[i].childNodes.length > 0){
						control.getReminderCache().reloadReminders(list.childNodes[i]);
					}
				}else if(list.childNodes[i].tagName == "comments"){
					if(list.childNodes[i].childNodes.length > 0){
						control.getCommentCache().reloadComments(list.childNodes[i]);
					}
				}else if(list.childNodes[i].tagName == "forms"){
					if(list.childNodes[i].childNodes.length > 0){
						control.getFormManager().reloadForms(list.childNodes[i]);
					}
				}else if(list.childNodes[i].tagName == "sync"){
					if($def(control.getSyncManager)){
						control.getSyncManager().reloadSync(list.childNodes[i]);
					}
				}else if(list.childNodes[i].tagName == "calendars"){
					if(list.childNodes[i].childNodes.length > 0){
						control.getCalendarCache().reloadCalendars(list.childNodes[i]);
					}
				}else if(list.childNodes[i].tagName == "settings"){
					control.getSettingsManager().reloadSettings(list.childNodes[i]);
				}else if(list.childNodes[i].tagName == "deleted"){
					// here we refresh items that have been deleted
					//
					// the xml is of the format <type>id</type>
					// for instance,
					// <event>3423</event>
					// or <calendar>452</calendar>
					try{
						var list2 = list.childNodes[i];
						for(var j=0;j<list2.childNodes.length;j++){
							if(list2.childNodes[i].tagName == "event"){
								var event_id = parseInt(list2.childNodes[j].nodeValue);
								// delete event event_id
								var event = control.getEventCache().getTaskSilent(task_id);
								control.getEventCache().notifyDeletingEvent(event);
								control.getEventCache().notifyDoneDeletingEvent(event);
							}else if(list2.childNodes[i].tagName == "task"){
								var task_id = parseInt(list2.childNodes[j].nodeValue);
								// delete event event_id
								var task = control.getEventCache().getTaskSilent(task_id);
								control.getEventCache().notifyDeletingTask(task);
								control.getEventCache().notifyDoneDeletingTask(task);
							}else if(list2.childNodes[i].tagName == "calendar"){
								var cal_id = parseInt(list2.childNodes[j].nodeValue);
								// delete event event_id
								var cal = control.getCalendarCache().getCalendar(cal_id);
								if($obj(cal) && cal != null){
									control.getCalendarCache().notifyDeletingCalendar(cal);
									control.getCalendarCache().notifyDoneDeletingCalendar(cal);
								}
							}

						}
					}catch(e){
						alert("exception refreshing deleted items: " + e);
					}
				}
			}

			// lets keep track of the last time that we successfully
			// refreshed everything.
			last_refresh = control.getSettingsManager().getGMT();
			that.notifyDoneRefreshing();
//			alert("refreshed at:" + last_refresh);
		}catch(e){
			alert("refresh.js:loadXML: " + e);
		}
	}

	function loadFail(){
		that.notifyRefreshingFailed();
		// bummmer!
		//
		// we won't do anything here yet :(
	}

	this.loadRemoteXML = function(list){
		that.notifyRefreshing();
		loadXML(list);
	}


	/******************************************
	 * listener functions
	 ******************************************/
	this.addListener = function(list){
		listeners.push(list);
	}

	/**
	 * removes a listener
	 */
	this.removeListener = function(list){
		for(var i=0;i<listeners.length;i++){
			if(listeners[i] == list){
				listeners.splice(i, 1);
			}
		}
	}


	/**
	 * notify listeners that we're refreshing
	 */
	this.notifyRefreshing = function(){
		for(var i=0;i<listeners.length;i++){
			listeners[i].refreshing();
		}
	}

	/**
	 * notify listeners that we're done refreshing
	 */
	this.notifyDoneRefreshing = function(){
		for(var i=0;i<listeners.length;i++){
			listeners[i].doneRefreshing();
		}
	}

	/**
	 * notify listeners that we're refreshing failed
	 */
	this.notifyRefreshingFailed = function(){
		for(var i=0;i<listeners.length;i++){
			listeners[i].refreshingFailed();
		}
	}


	//
	//
	// listen to the login manager
	//
	//
	var list = new Object();
	list.loginOk = function (){
		// we've logged in successfully
		// after being logged out.
		//
		// now we need to refresh from
		// last_session_date
		//
		// actually, we don't need to
		// b/c we were trying to refresh
		// when we found out that we're
		// logged out, and that request
		// will get resent automatically
		// when we get logged back in,
		// so lets not do it twice.
		//


		var dh = new jive.model.DateHelper(control);
		if(last_session_date.getTime() < last_refresh.getTime()){
			var dt = last_session_date;
		}else{
			var dt = last_refresh;
		}
		sendAjaxRefresh(dt, true);
	}
	list.loginFail = function(){
		// we don't care about this.
	}
	control.getLoginManager().addListener(list);

}

jive.model.SettingsManager = function(control){
    /**
     * returns the current GMT time
     */
    var last_gmt = null;
    var last_gmt_stamp = (new Date()).getTime();
    this.getGMT = function(){
        var d = new Date();
        if(last_gmt != null && d.getTime() < last_gmt_stamp + 500){
            return last_gmt;
        }
        var d = new Date();
        var now = new Date();
        // get the current GMT time
        // but chop off teh "GMT" at the end of the toString
        now.setTime(Date.parse(d.toUTCString().substring(0, d.toUTCString().length - 3)));
//		var now = new Date(d.toUTCString());
//		now.setFullYear(d.getUTCFullYear());
//		now.setMonth(d.getUTCMonth());
//		now.setDate(d.getUTCDate());
//		now.setHours(d.getUTCHours());
//		now.setMinutes(d.getUTCMinutes());
//		now.setSeconds(d.getUTCSeconds());

//		now.setFullYear(d.getUTCFullYear());
//		now.setMonth(d.getUTCMonth());
//		now.setDate(d.getUTCDate());
//		now.setHours(d.getUTCHours());
//		now.setMinutes(d.getUTCMinutes());
//		now.setSeconds(d.getUTCSeconds());

        last_gmt = now;
        return now;
    }

    this.getNOW = function(){
        return new Date();
    }

    this.getStartWeekOn = function(){
        return 1;
    }

    this.getSmartShading = function(){
        return true;
    }

    /**
     * return the URL to the weather icon for the input date
     * @param dt
     */
    this.getWeatherImage = function(dt){
        return "";
    }

    this.getDateFormat = function(){
        return "4/30";
    }

    this.getPreferredEditorMode = function() {
        if (preferredMode == "" && $def(WikiTextConverter)) {
            WikiTextConverter.getPreferredEditorMode(
                {
                    callback: function(mode) {
                        preferredMode = mode;
                    },
                    timeout: DWRTimeout, // 20 seconds
                    errorHandler: editorErrorHandler
                }
            );
        }

        if (preferredMode == "") {
            preferredMode = "text";
        }
        return preferredMode;
    }
    
}

jive.model.User = function(){

    var that = this;

    /**
     * if saving the settings fails,
     * then the revert() will be called
     * and we'll need to revert all our values
     *
     * this array will hold thunks that can
     * revert the changes
     */
    var revert_actions = new Array();

    function createRevertAction(func, value){
        return function(){ func(value); }
    }
    /**
     * we don't need to save our revert actions anymore
     * probably b/c a save to DB went ok,
     * so set it as an emtpy array
     */
    this.clearRevertActions = function(){
        revert_actions = new Array();
    }
    /**
     * we need to revert to our old values
     * probably b/c a save to the DB when wrong
     */
    this.revert = function(){
        while(revert_actions.length > 0){
            revert_actions[0]();
            revert_actions.splice(0,1);
        }
    }

    //
    // properties
    var id;
    var username;
    var fullname;
    var url;

    this.getID = function(){
        return id;
    }
    this.getUsername = function(){
        return username;
    }
    this.getFullName = function(){
        return fullname;
    }
    this.getURL = function(){
        return url;
    }

    this.setID = function(i){
        id = i;
    }
    this.setUsername = function(n){
        username = n;
    }
    this.setFullName= function(n){
        revert_actions.push(createRevertAction(function(val){ fullname = val; }, fullname));
        fullname = n;
    }
    this.setURL = function(u){
        url = u;
    }
    /**
     * This function removes all setters for
     * properties that should never be reset after
     * the object has been loaded into the cache.
     *
     * physically removing these functions after
     * init will help us stop devs from calling
     * them accidentally.
     */
    this.cleanAfterInit = function(){
        that.clearRevertActions();
        that.setID = null;
        that.setUsername = null;
        this.setURL = null;
    }
}


jive.model.UserCache = function(control){

    var that = this;

    var cache = new jive.ext.y.HashTable();


    function refreshUserInCache(user){
        var u = cache.get(user.getID());
        if($obj(u)){
            u.setFullName(user.getFullName());
            u.clearRevertActions();
        }else{
            cache.put(user.getID(), user);
        }
        that.notifyLoadUser(user);
    }

    function loadUserXML(list){
        var user = new jive.model.User();
        for(var j=0;j<list.childNodes.length;j++){
            if(list.childNodes[j].tagName == "i"){
                if(list.childNodes[j].childNodes.length > 0)
                    user.setID(parseInt(list.childNodes[j].childNodes[0].nodeValue));
            }else if(list.childNodes[j].tagName == "u"){
                user.setUsername(list.childNodes[j].childNodes[0].nodeValue);
            }else if(list.childNodes[j].tagName == "n"){
                user.setFullName(list.childNodes[j].childNodes[0].nodeValue);
            }else if(list.childNodes[j].tagName == "url"){
                user.setURL(list.childNodes[j].childNodes[0].nodeValue);
            }
        }
        user.cleanAfterInit();
        refreshUserInCache(user);
        return user;
    }


    //
    // expects a <user> tag
    this.loadExternalUser = function(list){
        that.notifyLoadBegin();
        try{
            var u = loadUserXML(list);
            that.notifyLoadFinish();
            return u;
        }catch(e){
            that.notifyLoadFail();
        }
        return null;
    }

    /******************************************
     * listener functions
     ******************************************/
    this.addListener = function(list){
        listeners.push(list);
    }

    var listeners = new Array();
    var listener_actions = new Array();
    /**
     * act must be a thunk (a function without arguments)
     * it will be executed after either
     * notifyLoadFinish or notifyLoadFail
     */
    this.addListenerAction = function(act){
        listener_actions.push(act);
    }

    /**
     * private
     * executes all the listener actions
     */
    this.executeListenerActions = function(){
        while(listener_actions.length > 0){
            listener_actions[0]();
            listener_actions.splice(0,1);
        }
    }

    this.removeListener = function(list){
        for(var i=0;i<listeners.length;i++){
            if(listeners[i] == list){
                listeners.splice(i, 1);
            }
        }
    }

    /**
     * notification functions
     */
    this.notifyLoadUser = function(p){
        for(var i=0;i<listeners.length;i++){
            listeners[i].loadUser(p);
        }
        that.executeListenerActions();
    }

    this.notifyLoadBegin = function(){
        for(var i=0;i<listeners.length;i++){
            listeners[i].beginLoadingUsers();
        }
        that.executeListenerActions();
    }

    this.notifyLoadFinish = function(){
        for(var i=0;i<listeners.length;i++){
            listeners[i].doneLoadingUsers();
        }
        that.executeListenerActions();
    }

	this.notifyLoadFail = function(){
		for(var i=0;i<listeners.length;i++){
			listeners[i].loadingUsersFailed();
		}
		that.executeListenerActions();
	}

}

jive.model.isCheckPoint = function(cp){
    return $def(cp) && $obj(cp) && cp != null && $def(cp.isCheckPoint);
}

jive.model.CheckPoint = function(p){
    var that = this;

    /**
     * if saving the settings fails,
     * then the revert() will be called
     * and we'll need to revert all our values
     *
     * this array will hold thunks that can
     * revert the changes
     */
    var revert_actions = new Array();

    function createRevertAction(func, value){
        return function(){ func(value); }
    }
    /**
     * we don't need to save our revert actions anymore
     * probably b/c a save to DB went ok,
     * so set it as an emtpy array
     */
    this.clearRevertActions = function(){
        revert_actions = new Array();
    }
    /**
     * we need to revert to our old values
     * probably b/c a save to the DB when wrong
     */
    this.revert = function(){
        while(revert_actions.length > 0){
            revert_actions[0]();
            revert_actions.splice(0,1);
        }
    }

    var proj = p;

    this.getProject = function(){
        return proj;
    }

    var id;
    var created_on = null;
    var last_modified = null;
    var name = "";
    var desc = "";
    var due = null;

    this.isCheckPoint = function(){ return true; };    

    this.getID = function(){
        return id;
    }
    this.getCreatedOn = function(){
        return created_on;
    }
    this.getLastModifiedOn = function(){
        return last_modified;
    }
    this.getName = function(){
        return name;
    }
    this.getDescription = function(){
        return desc;
    }
    this.getDueDate = function(){
        return due;
    }

    this.setID = function(i){
        id = i;
    }
    this.setCreatedOn = function(n){
        created_on = n;
    }
    this.setLastModifiedOn = function(n){
        last_modified = n;
    }
    this.setName = function(n){
        revert_actions.push(createRevertAction(function(val){ name = val; }, name));
        name = n;
    }
    this.setDescription = function(d){
        revert_actions.push(createRevertAction(function(val){ desc = val; }, desc));
        desc = d;
    }
    this.setDueDate = function(d){
        revert_actions.push(createRevertAction(function(val){ due = val; }, due));
        due = d;
    }


    /**
     * This function removes all setters for
     * properties that should never be reset after
     * the object has been loaded into the cache.
     *
     * physically removing these functions after
     * init will help us stop devs from calling
     * them accidentally.
     */
    this.cleanAfterInit = function(){
        that.clearRevertActions();
        that.setID = null;
        that.setCreatedOn = null;
        that.setLastModifiedOn = null;
    }

}


jive.model.Project = function(){

    var that = this;

    /**
     * if saving the settings fails,
     * then the revert() will be called
     * and we'll need to revert all our values
     *
     * this array will hold thunks that can
     * revert the changes
     */
    var revert_actions = new Array();

    function createRevertAction(func, value){
        return function(){ func(value); }
    }
    /**
     * we don't need to save our revert actions anymore
     * probably b/c a save to DB went ok,
     * so set it as an emtpy array
     */
    this.clearRevertActions = function(){
        revert_actions = new Array();
    }
    /**
     * we need to revert to our old values
     * probably b/c a save to the DB when wrong
     */
    this.revert = function(){
        while(revert_actions.length > 0){
            revert_actions[0]();
            revert_actions.splice(0,1);
        }
    }

    //
    // properties
    var id;
    var name = "";
    var desc = "";
    var creator;
    var due;
    var last_modified;
    var tasks;
    var editable = false;
    var cps = new Array();

    this.getID = function(){
        return id;
    }
    this.getCreator = function(){
        return creator;
    }
    this.getLastModifiedOn = function(){
        return last_modified;
    }
    this.getName = function(){
        return name;
    }
    this.getDescription = function(){
        return desc;
    }
    this.getDueDate = function(){
        return due;
    }
    this.getTasks = function(){
        return tasks;
    }
    this.isEditable = function(){
        return editable;
    }
    this.getCheckPoints = function(){
        return cps;
    }

    this.setID = function(i){
        id = i;
    }
    this.setCreator = function(i){
        creator = i;
    }
    this.setEditable = function(b){
        editable = b;
    }
    this.setLastModifiedOn = function(n){
        last_modified = n;
    }
    this.setName = function(n){
        revert_actions.push(createRevertAction(function(val){ name = val; }, name));
        name = n;
    }
    this.setDescription = function(d){
        revert_actions.push(createRevertAction(function(val){ desc = val; }, desc));
        desc = d;
    }
    this.setDueDate = function(d){
        revert_actions.push(createRevertAction(function(val){ due = val; }, due));
        due = d;
    }
    this.setTasks = function(t){
        tasks = t;
    }
    this.setCheckPoints = function(c){
        cps = c;
    }

    /**
     * This function removes all setters for
     * properties that should never be reset after
     * the object has been loaded into the cache.
     *
     * physically removing these functions after
     * init will help us stop devs from calling
     * them accidentally.
     */
    this.cleanAfterInit = function(){
        that.clearRevertActions();
        that.setID = null;
        that.setCreator = null;
        that.setEditable = null;
        that.setLastModifiedOn = null;
        that.setCheckPoints = null;
    }
}


jive.model.ProjectCache = function(control){

    var that = this;

    var cache = new jive.ext.y.HashTable();

    this.getProject = function(pID){
        return cache.get(pID);
    }

    function refreshProjectInCache(proj){
        var p = cache.get(proj.getID());
        if($obj(p)){
            p.setCreator(proj.getCreator());
            p.setDescription(proj.getDescription());
            p.setDueDate(proj.getDueDate());
            p.clearRevertActions();
        }else{
            cache.put(proj.getID(), proj);
        }
        that.notifyLoadProject(proj);
    }

    function loadProjectsXML(list){
        var ret = new Array();
        for(var i=0;i<list.childNodes.length;i++){
            var proj = new jive.model.Project();
            var list2 = list.childNodes[i];
            var cps = new Array();
            for(var j=0;j<list2.childNodes.length;j++){
                if(list2.childNodes[j].tagName == "id"){
                    if(list2.childNodes[j].childNodes.length > 0)
                        proj.setID(list2.childNodes[j].childNodes[0].nodeValue);
                }else if(list2.childNodes[j].tagName == "name"){
                    if(list2.childNodes[j].childNodes.length > 0)
                        proj.setName(list2.childNodes[j].childNodes[0].nodeValue);
                }else if(list2.childNodes[j].tagName == "desc"){
                    if(list2.childNodes[j].childNodes.length > 0)
                        proj.setDescription(list2.childNodes[j].childNodes[0].nodeValue);
                }else if(list2.childNodes[j].tagName == "creator"){
                    var list3 = list2.childNodes[j];
                    var u = control.getUserCache().loadExternalUser(list3.childNodes[0]);
                    proj.setCreator(u);
                }else if(list2.childNodes[j].tagName == "d_on"){
                    var dt = list2.childNodes[j].childNodes[0].nodeValue;
                    if(dt != null){
                        proj.setDueDate(new Date(dt.replace(/-/g,"/")));
                    }else{
                        proj.setDueDate(null)
                    }
                }else if(list2.childNodes[j].tagName == "m_on"){
                    var dt = list2.childNodes[j].childNodes[0].nodeValue;
                    if(dt != null){
                        proj.setLastModifiedOn(new Date(dt.replace(/-/g,"/")));
                    }else{
                        proj.setLastModifiedOn(null)
                    }
                }else if(list2.childNodes[j].tagName == "editable"){
                    proj.setEditable(true);
                }else if(list2.childNodes[j].tagName == "tasks"){
                    var list3 = list2.childNodes[j];
                    var u = control.getTaskCache().loadExternalTasks(list3);
                    proj.setTasks(u);
                }else if(list2.childNodes[j].tagName == "cps"){
                    var list3 = list2.childNodes[j];
                    for(var k=0;k<list3.childNodes.length;k++){
                        var listcp = list3.childNodes[k];
                        var cp = new jive.model.CheckPoint(proj);
                        for(var l=0;l<listcp.childNodes.length;l++){
                            if(listcp.childNodes[l].tagName == "id"){
                                if(listcp.childNodes[l].childNodes.length > 0)
                                    cp.setID(listcp.childNodes[l].childNodes[0].nodeValue);
                            }else if(listcp.childNodes[l].tagName == "c_on"){
                                var dt = listcp.childNodes[l].childNodes[0].nodeValue;
                                if(dt != null){
                                    cp.setCreatedOn(new Date(dt.replace(/-/g,"/")));
                                }else{
                                    cp.setCreatedOn(null)
                                }
                            }else if(listcp.childNodes[l].tagName == "m_on"){
                                var dt = listcp.childNodes[l].childNodes[0].nodeValue;
                                if(dt != null){
                                    cp.setLastModifiedOn(new Date(dt.replace(/-/g,"/")));
                                }else{
                                    cp.setLastModifiedOn(null)
                                }
                            }else if(listcp.childNodes[l].tagName == "nm"){
                                if(listcp.childNodes[l].childNodes.length > 0)
                                    cp.setName(listcp.childNodes[l].childNodes[0].nodeValue);
                            }else if(listcp.childNodes[l].tagName == "desc"){
                                if(listcp.childNodes[l].childNodes.length > 0)
                                    cp.setName(listcp.childNodes[l].childNodes[0].nodeValue);
                            }else if(listcp.childNodes[l].tagName == "due"){
                                if(listcp.childNodes[l].childNodes.length > 0){
                                    var dt = listcp.childNodes[l].childNodes[0].nodeValue;
                                    if(dt != null){
                                        cp.setDueDate(new Date(dt.replace(/-/g,"/")));
                                    }else{
                                        cp.setDueDate(null)
                                    }
                                }
                            }
                        }
                        cps.push(cp);
                        proj.setCheckPoints(cps);
                    }
                }
            }
            proj.cleanAfterInit();
            refreshProjectInCache(proj);
            ret.push(proj);
        }
        return ret;
    }

    //
    // expects a <projects> tag
    this.loadExternalProjects = function(list){
        that.notifyLoadBegin();
        try{
            var ret = loadProjectsXML(list);
            that.notifyLoadFinish();
            return ret;
        }catch(e){
            that.notifyLoadFail();
        }
        return null;
    }

    /**
     * return true if the user can edit the project
     * false otherwise
     * @param pID the id of the project
     */
    this.canEditProjectHuh = function(pID){
        if(pID == 0) return true;
    }

    /******************************************
     * listener functions
     ******************************************/
    this.addListener = function(list){
        listeners.push(list);
    }

    var listeners = new Array();
    var working = 0;
    var listener_actions = new Array();
    /**
     * act must be a thunk (a function without arguments)
     * it will be executed after either
     * notifyLoadFinish or notifyLoadFail
     */
    this.addListenerAction = function(act){
        listener_actions.push(act);
    }

    /**
     * private
     * executes all the listener actions
     */
    this.executeListenerActions = function(){
        while(listener_actions.length > 0){
            listener_actions[0]();
            listener_actions.splice(0,1);
        }
    }

    this.removeListener = function(list){
        if(working == 0){
            for(var i=0;i<listeners.length;i++){
                if(listeners[i] == list){
                    listeners.splice(i, 1);
                }
            }
        }else{
            that.addListenerAction(function(list){
                return function(){
                   that.removeListener(list);
                }
            }(list));
        }
    }

    /**
     * notification functions
     */
    this.notifyLoadProject = function(p){
        working++;
        for(var i=0;i<listeners.length;i++){
            listeners[i].loadProject(p);
        }
        working--;
        that.executeListenerActions();
    }

    this.notifyLoadBegin = function(){
        working++;
        for(var i=0;i<listeners.length;i++){
            listeners[i].beginLoadingProjects();
        }
        working--;
        that.executeListenerActions();
    }

    this.notifyLoadFinish = function(){
        working++;
        for(var i=0;i<listeners.length;i++){
            listeners[i].doneLoadingProjects();
        }
        working--;
        that.executeListenerActions();
    }

	this.notifyLoadFail = function(){
        working++;
		for(var i=0;i<listeners.length;i++){
			listeners[i].loadingProjectsFailed();
		}
        working--;
		that.executeListenerActions();
	}

}

jive.model.ProjectCacheListener = function(){
    this.loadProject = function(p){ }
    this.beginLoadingProjects = function(){ }
    this.doneLoadingProjects = function(){ }
    this.loadingProjectsFailed = function(){ }
}

jive.model.isDocument = function(t){
    return $obj(t) && t != null && $def(t.getBody) && $def(t.getSubject)
}

jive.model.Document = function(){

    var that = this;


    /**
     * if saving the settings fails,
     * then the revert() will be called
     * and we'll need to revert all our values
     *
     * this array will hold thunks that can
     * revert the changes
     */
    var revert_actions = new Array();

    function createRevertAction(func, value){
        return function(){ func(value); }
    }
    /**
     * we don't need to save our revert actions anymore
     * probably b/c a save to DB went ok,
     * so set it as an emtpy array
     */
    this.clearRevertActions = function(){
        revert_actions = new Array();
    }
    /**
     * we need to revert to our old values
     * probably b/c a save to the DB when wrong
     */
    this.revert = function(){
        while(revert_actions.length > 0){
            revert_actions[0]();
            revert_actions.splice(0,1);
        }
    }

    this.confirm = function(){
        that.notifyTaskChanged();
    }
    
    //
    // properties
    var id;
    var html;

    this.getID = function(){
        return id;
    }
    this.getHTML = function(){
        return html;
    }

    this.setID = function(i){
        id = i;
    }
    this.setHTML = function(h){
        revert_actions.push(createRevertAction(function(val){ html = val; }, html));
        html = h;
    }
    /**
     * This function removes all setters for
     * properties that should never be reset after
     * the object has been loaded into the cache.
     *
     * physically removing these functions after
     * init will help us stop devs from calling
     * them accidentally.
     */
    this.cleanAfterInit = function(){
        that.clearRevertActions();
        that.setID = null;
    }

    /**
     * converts this document's HTML to wiki
     * format
     * @param list
     */
    this.convertToWiki = function(){
        objectLookupSessionKey
    }

    /******************************************
     * listener functions
     ******************************************/
    var listeners = new Array();
    
    this.addListener = function(list){
        listeners.push(list);
    }

    /**
     * removes a listener
     */
    this.removeListener = function(list){
        for(var i=0;i<listeners.length;i++){
            if(listeners[i] == list){
                listeners.splice(i, 1);
            }
        }
    }


    /**
     * notify listeners that the task has changed
     */
    this.notifyDocumentChanged = function(){
        for(var i=0;i<listeners.length;i++){
            listeners[i].documentChanged(that);
        }
    }
    /******************************************
     * end listener functions
     ******************************************/


}


jive.model.DocumentCache = function(control){

    var that = this;

    var cache = new jive.ext.y.HashTable();

    /**
     * define a task listener class
     * this will listen to task objects that are
     * added to this cache.
     *
     * if any task changes, then we'll update our listeners
     * about it
     */
    function DocumentListener(){
        this.documentChanged = function(doc){
            that.notifyDocumentChanged(doc);
        }
    }

    function refreshDocumentInCache(ttask){
        var t = cache.get(ttask.getID());
        if($obj(t)){
            //
            //  update properties of doc  here
            //
            t.clearRevertActions();
        }else{
            ttask.addListener(new DocumentListener());
            cache.put(ttask.getID(), ttask);
        }
        that.notifyLoadDocument(ttask);
    }

    /**
     * save the calendar to the DB
     */
    this.saveDocument= function(ttask){
        that.notifySavingDocument(ttask);
        try{
            var settings = control.getSettingsManager();
            var dh = new jive.model.DateHelper(control);
            var a = control.newAjax(
                function(list){
                    try{
                        if(list.tagName == "success"){
                            that.notifyDoneSavingDocument(ttask);
                        }else{
                            that.notifySavingDocumentFailed(ttask);
                        }
                    }catch(e){
                        alert(e);
                    }
                },
                function(){
                    try{
                        that.notifySavingDocumentFailed(ttask);
                    }catch(e){
                        alert("saving failed: " + e);
                    }
                });

            // save document here
//            a.POST(HOSTURL + AJAXPATH + "?save_task","task_id=" + encodeURIComponent(ttask.getID()) + "&due=" + encodeURIComponent(due) + "&status=" + encodeURIComponent(status) + "&title=" + encodeURIComponent(title) + "&description=" + encodeURIComponent(description) + "&never_due=" + encodeURIComponent(nd ? "1" : "0") + "&project_id=" + projID);
        }catch(e){
            that.notifySavingDocumentFailed(ttask);
        }
    }

    function newDocumentFromWikiHelper(html){
        that.notifyNewDocumentFromWiki(new jive.model.Document("",html));
    }

    /**
     * Creates a document object from wiki markup
     * (the document is loaded in after AJAX
     * @param list
     */
    this.newDocumentFromWiki = function(wiki){
        if(!$def(window.objectLookupSessionKey)){
            throw "window.objectLookupSessionKey must be defined to use newDocumentFromWiki()";
        }
        if(!$def(WikiTextConverter)){
            throw "WikiTextConverter must be defined to use newDocumentFromWiki()";
        }
        WikiTextConverter.convertFromWiki(wiki, window.objectLookupSessionKey,
            {
                callback: newDocumentFromWikiHelper,
                timeout: DWRTimeout, // 20 seconds
                errorHandler: that.notifyNewDocumentFromWikiFailed
            }
        );
    }


    function loadTasksXML(list){
        var ret = new Array();
        for(var i=0;i<list.childNodes.length;i++){
            var task = new jive.model.Document();
            var list2 = list.childNodes[i];
            for(var j=0;j<list2.childNodes.length;j++){
                if(list2.childNodes[j].tagName == "id"){
                    if(list2.childNodes[j].childNodes.length > 0)
                        task.setID(list2.childNodes[j].childNodes[0].nodeValue);
                }
            }
            ret.push(task);
            task.cleanAfterInit();
            refreshDocumentInCache(task);
        }
        return ret;
    }


    //
    // expects a <tasks> tag
    this.loadExternalDocuments = function(list){
        that.notifyLoadBegin();
        try{
            var u = loadDocumentsXML(list);
            that.notifyLoadFinish();
            return u;
        }catch(e){
            that.notifyLoadFail();
        }
        return null;
    }

    /******************************************
     * listener functions
     ******************************************/
    this.addListener = function(list){
        listeners.push(list);
    }

    var listeners = new Array();
    var listener_actions = new Array();
    /**
     * act must be a thunk (a function without arguments)
     * it will be executed after either
     * notifyLoadFinish or notifyLoadFail
     */
    this.addListenerAction = function(act){
        listener_actions.push(act);
    }

    /**
     * private
     * executes all the listener actions
     */
    this.executeListenerActions = function(){
        while(listener_actions.length > 0){
            listener_actions[0]();
            listener_actions.splice(0,1);
        }
    }

    this.removeListener = function(list){
        for(var i=0;i<listeners.length;i++){
            if(listeners[i] == list){
                listeners.splice(i, 1);
            }
        }
    }

    /**
     * notification functions
     */
    this.notifyDocumentChanged = function(p){
        for(var i=0;i<listeners.length;i++){
            listeners[i].documentChanged(p);
        }
        that.executeListenerActions();
    }

    this.notifyLoadDocument = function(p){
        for(var i=0;i<listeners.length;i++){
            listeners[i].loadDocument(p);
        }
        that.executeListenerActions();
    }

    this.notifyLoadBegin = function(){
        for(var i=0;i<listeners.length;i++){
            listeners[i].beginLoadingDocuments();
        }
        that.executeListenerActions();
    }

    this.notifyLoadFinish = function(){
        for(var i=0;i<listeners.length;i++){
            listeners[i].doneLoadingDocuments();
        }
        that.executeListenerActions();
    }

	this.notifyLoadFail = function(){
		for(var i=0;i<listeners.length;i++){
			listeners[i].loadingDocumentsFailed();
		}
		that.executeListenerActions();
	}

    this.notifySavingDocument = function(p){
        for(var i=0;i<listeners.length;i++){
            listeners[i].savingDocument(p);
        }
        that.executeListenerActions();
    }

    this.notifyDoneSavingDocument = function(p){
        for(var i=0;i<listeners.length;i++){
            listeners[i].doneSavingDocument(p);
        }
        that.executeListenerActions();
    }

    this.notifySavingDocumentFailed = function(p){
        for(var i=0;i<listeners.length;i++){
            listeners[i].savingDocumentFailed(p);
        }
        that.executeListenerActions();
    }
    this.notifyNewDocumentFromWiki = function(doc){
        for(var i=0;i<listeners.length;i++){
            listeners[i].newDocumentFromWiki(doc);
        }
        that.executeListenerActions();
    }
    this.notifyNewDocumentFromWikiFailed = function(){
        for(var i=0;i<listeners.length;i++){
            listeners[i].newDocumentFromWikiFailed(p);
        }
        that.executeListenerActions();
    }

    var list = new jive.model.DocumentCacheListener();
    list.documentChanged = function(t){
        that.saveDocument(t);
    }
    that.addListener(list);

}

jive.model.DocumentCacheListener = function(){
    this.loadingDocumentsFailed = function(){ }
    this.doneLoadingDocuments = function(){ }
    this.beginLoadingDocuments = function(){ }
    this.loadDocument = function(){ }
    this.documentChanged = function(){ }
    this.savingDocument = function(){ }
    this.doneSavingDocument = function(){ }
    this.savingDocumentFailed = function(){ }
    this.newDocumentFromWikiFailed = function(){ }
}

jive.model.TaskCacheListener = function(){
    this.loadingTasksFailed = function(){ }
    this.doneLoadingTasks = function(){ }
    this.beginLoadingTasks = function(){ }
    this.loadTask = function(){ }
    this.taskChanged = function(){ }
}

jive.model.isEvent = function(t){
    return $obj(t) && t != null && $def(t.getStart) && $def(t.getEnd)
}

jive.model.isTask = function(t){
    return $obj(t) && t != null && $def(t.getDueDate) && $def(t.getProjectID)
}

jive.model.Task = function(){

    var that = this;

    var PERSONAL = 0;

    /**
     * if saving the settings fails,
     * then the revert() will be called
     * and we'll need to revert all our values
     *
     * this array will hold thunks that can
     * revert the changes
     */
    var revert_actions = new Array();

    function createRevertAction(func, value){
        return function(){ func(value); }
    }
    /**
     * we don't need to save our revert actions anymore
     * probably b/c a save to DB went ok,
     * so set it as an emtpy array
     */
    this.clearRevertActions = function(){
        revert_actions = new Array();
    }
    /**
     * we need to revert to our old values
     * probably b/c a save to the DB when wrong
     */
    this.revert = function(){
        while(revert_actions.length > 0){
            revert_actions[0]();
            revert_actions.splice(0,1);
        }
    }

    this.confirm = function(){
        that.notifyTaskChanged();
    }
    
    //
    // properties
    var id;
    var project_id = PERSONAL; // personal task
    var due = null;
    var subject;
    var description;
    var created_by;
    var created_on;
    var assigned_to;
    var assigned_by;
    var complete;
    var url;

    this.getID = function(){
        return id;
    }
    this.getProjectID = function(){
        return project_id;
    }
    this.getDueDate = function(){
        return due;
    }
    this.hasDueDate = function(){
        return due != null;
    }
    this.getSubject = function(){
        return subject;
    }
    this.getDescription = function(){
        return description;
    }
    this.getCreatedBy = function(){
        return created_by;
    }
    this.getCreatedOn = function(){
        return created_on;
    }
    this.getAssignedBy = function(){
        return assigned_by;
    }
    this.getAssignedTo = function(){
        return assigned_to;
    }
    this.getURL = function(){
        return url;
    }
    this.isComplete = function(){
        return complete;
    }

    this.setID = function(i){
        id = i;
    }
    this.setProjectID = function(i){
        project_id = i;
    }
    this.setCreatedBy = function(n){
        created_by = n;
    }
    this.setCreatedOn = function(n){
        created_on = n;
    }
    this.setComplete = function(b){
        complete = b;
    }
    this.setURL = function(u){
        url = u;
    }


    this.setDueDate = function(d){
        revert_actions.push(createRevertAction(function(val){ due = val; }, due));
        due = d;
    }
    this.setSubject = function(n){
        revert_actions.push(createRevertAction(function(val){ subject = val; }, subject));
        subject = n;
    }
    this.setDescription = function(d){
        revert_actions.push(createRevertAction(function(val){ description = val; }, description));
        description = d;
    }
    this.setAssignedBy = function(u){
        revert_actions.push(createRevertAction(function(val){ assigned_by = val; }, assigned_by));
        assigned_by = u;
    }
    this.setAssignedTo = function(d){
        revert_actions.push(createRevertAction(function(val){ assigned_to = val; }, assigned_to));
        assigned_to = d;
    }
    /**
     * This function removes all setters for
     * properties that should never be reset after
     * the object has been loaded into the cache.
     *
     * physically removing these functions after
     * init will help us stop devs from calling
     * them accidentally.
     */
    this.cleanAfterInit = function(){
        that.clearRevertActions();
        that.setID = null;
        that.setCreatedBy = null;
        that.setCreatedOn = null;
        that.setURL = null;
    }


    /******************************************
     * listener functions
     ******************************************/
    var listeners = new Array();
    
    this.addListener = function(list){
        listeners.push(list);
    }

    /**
     * removes a listener
     */
    this.removeListener = function(list){
        for(var i=0;i<listeners.length;i++){
            if(listeners[i] == list){
                listeners.splice(i, 1);
            }
        }
    }


    /**
     * notify listeners that the task has changed
     */
    this.notifyTaskChanged = function(){
        for(var i=0;i<listeners.length;i++){
            listeners[i].taskChanged(that);
        }
    }
    /******************************************
     * end listener functions
     ******************************************/


}


jive.model.TaskCache = function(control){

    var that = this;

    var cache = new jive.ext.y.HashTable();

    /**
     * define a task listener class
     * this will listen to task objects that are
     * added to this cache.
     *
     * if any task changes, then we'll update our listeners
     * about it
     */
    function TaskListener(){
        this.taskChanged = function(ttask){
            that.notifyTaskChanged(ttask);
        }
    }

    function refreshTaskInCache(ttask){
        var t = cache.get(ttask.getID());
        if($obj(t)){
            t.setDueDate(ttask.getDueDate());
            t.setSubject(ttask.getSubject());
            t.setDescription(ttask.getDescription());
            t.setAssignedTo(ttask.getAssignedTo());
            t.setAssignedBy(ttask.getAssignedBy());
            t.clearRevertActions();
        }else{
            ttask.addListener(new TaskListener());
            cache.put(ttask.getID(), ttask);
        }
        that.notifyLoadTask(ttask);
    }

    /**
     * save the calendar to the DB
     */
    this.saveTask= function(ttask){
        that.notifySavingTask(ttask);
        try{
            var settings = control.getSettingsManager();
            var dh = new jive.model.DateHelper(control);
            var a = control.newAjax(
                function(list){
                    try{
                        if(list.tagName == "success"){
                            that.notifyDoneSavingTask(ttask);
                        }else{
                            that.notifySavingTaskFailed(ttask);
                        }
                    }catch(e){
                        alert(e);
                    }
                },
                function(){
                    try{
                        that.notifySavingTaskFailed(ttask);
                    }catch(e){
                        alert("saving failed: " + e);
                    }
                });
            var due      = ttask.getDueDate();
            due          = (due != null) ? dh.formatToDateTime(due) : "";
            var nd = ! ttask.hasDueDate();
            var status   = ttask.getStatus();
            var title = ttask.getSubject();
            var description = ttask.getDescription();
            var projID = ttask.getProjectID();

            a.POST(HOSTURL + AJAXPATH + "?save_task","task_id=" + encodeURIComponent(ttask.getID()) + "&due=" + encodeURIComponent(due) + "&status=" + encodeURIComponent(status) + "&title=" + encodeURIComponent(title) + "&description=" + encodeURIComponent(description) + "&never_due=" + encodeURIComponent(nd ? "1" : "0") + "&project_id=" + projID);
        }catch(e){
            that.notifySavingTaskFailed(ttask);
        }
    }



    function loadTasksXML(list){
        var ret = new Array();
        for(var i=0;i<list.childNodes.length;i++){
            var task = new jive.model.Task();
            var list2 = list.childNodes[i];
            for(var j=0;j<list2.childNodes.length;j++){
                if(list2.childNodes[j].tagName == "id"){
                    if(list2.childNodes[j].childNodes.length > 0)
                        task.setID(list2.childNodes[j].childNodes[0].nodeValue);
                }else if(list2.childNodes[j].tagName == "pid"){
                        if(list2.childNodes[j].childNodes.length > 0)
                            task.setProjectID(list2.childNodes[j].childNodes[0].nodeValue);
                }else if(list2.childNodes[j].tagName == "due"){
                    var dt = list2.childNodes[j].childNodes[0].nodeValue;
                    if(dt != null){
                        task.setDueDate(new Date(dt.replace(/-/g,"/")));
                    }else{
                        task.setDueDate(null)
                    }
                }else if(list2.childNodes[j].tagName == "subj"){
                    task.setSubject(list2.childNodes[j].childNodes[0].nodeValue);
                }else if(list2.childNodes[j].tagName == "desc"){
                    task.setDescription(list2.childNodes[j].childNodes[0].nodeValue);
                }else if(list2.childNodes[j].tagName == "c_on"){
                    var dt = list2.childNodes[j].childNodes[0].nodeValue;
                    if(dt != null){
                        task.setCreatedOn(new Date(dt.replace(/-/g,"/")));
                    }else{
                        task.setCreatedOn(null)
                    }
                }else if(list2.childNodes[j].tagName == "c_by"){
                    task.setCreatedBy(control.getUserCache().loadExternalUser(list2.childNodes[j].childNodes[0]));
                }else if(list2.childNodes[j].tagName == "a_by"){
                    task.setAssignedBy(control.getUserCache().loadExternalUser(list2.childNodes[j].childNodes[0]));
                }else if(list2.childNodes[j].tagName == "a_to"){
                    task.setAssignedTo(control.getUserCache().loadExternalUser(list2.childNodes[j].childNodes[0]));
                }else if(list2.childNodes[j].tagName == "url"){
                    task.setURL(list2.childNodes[j].childNodes[0].nodeValue);
                }else if(list2.childNodes[j].tagName == "status"){
                    task.setComplete(list2.childNodes[j].nodeValue == "c");
                }
            }
            ret.push(task);
            task.cleanAfterInit();
            refreshTaskInCache(task);
        }
        return ret;
    }


    //
    // expects a <tasks> tag
    this.loadExternalTasks = function(list){
        that.notifyLoadBegin();
        try{
            var u = loadTasksXML(list);
            that.notifyLoadFinish();
            return u;
        }catch(e){
            that.notifyLoadFail();
        }
        return null;
    }

    /******************************************
     * listener functions
     ******************************************/
    this.addListener = function(list){
        listeners.push(list);
    }

    var listeners = new Array();
    var listener_actions = new Array();
    /**
     * act must be a thunk (a function without arguments)
     * it will be executed after either
     * notifyLoadFinish or notifyLoadFail
     */
    this.addListenerAction = function(act){
        listener_actions.push(act);
    }

    /**
     * private
     * executes all the listener actions
     */
    this.executeListenerActions = function(){
        while(listener_actions.length > 0){
            listener_actions[0]();
            listener_actions.splice(0,1);
        }
    }

    this.removeListener = function(list){
        for(var i=0;i<listeners.length;i++){
            if(listeners[i] == list){
                listeners.splice(i, 1);
            }
        }
    }

    /**
     * notification functions
     */
    this.notifyTaskChanged = function(p){
        for(var i=0;i<listeners.length;i++){
            listeners[i].taskChanged(p);
        }
        that.executeListenerActions();
    }

    this.notifyLoadTask = function(p){
        for(var i=0;i<listeners.length;i++){
            listeners[i].loadTask(p);
        }
        that.executeListenerActions();
    }

    this.notifyLoadBegin = function(){
        for(var i=0;i<listeners.length;i++){
            listeners[i].beginLoadingTasks();
        }
        that.executeListenerActions();
    }

    this.notifyLoadFinish = function(){
        for(var i=0;i<listeners.length;i++){
            listeners[i].doneLoadingTasks();
        }
        that.executeListenerActions();
    }

	this.notifyLoadFail = function(){
		for(var i=0;i<listeners.length;i++){
			listeners[i].loadingTasksFailed();
		}
		that.executeListenerActions();
	}


    var list = new jive.model.TaskCacheListener();
    list.taskChanged = function(t){
        that.saveTask(t);
    }
    that.addListener(list);

}

jive.rte.settings = function(id){

    function computeStyle(str){
        for(var i=0;i<jive.rte.defaultStyles.length;i++){
            str += "," + jive.rte.defaultStyles[i];
        }
        return str;
    }


    if(id=="mini-w-quote"){
        var _jive_image_picker_url = false;
        var ret = jive.rte.settings(0);
        ret.content_css = computeStyle(CS_BASE_URL +  "/resources/scripts/tiny_mce3/themes/advanced/skins/default/content.css");
        ret.theme_advanced_statusbar_location = "none";
        ret.theme_advanced_buttons1 = "bold,italic,underline,strikethrough,spacerbutton,bullist,numlist,spacerbutton,jiveimage,jivevideo,spacerbutton,jivelink,jiveemoticons,jivequote,spellchecker,html";
        delete ret.theme_advanced_buttons2;
        delete ret.theme_advanced_buttons3;
        ret.default_height = 29;
        return ret;
    }else if(id=="mini"){
        var _jive_image_picker_url = false;
        var ret = jive.rte.settings(0);
        ret.content_css = computeStyle(CS_BASE_URL +  "/resources/scripts/tiny_mce3/themes/advanced/skins/default/content.css");
        ret.theme_advanced_statusbar_location = "none";
        ret.theme_advanced_buttons1 = "bold,italic,underline,strikethrough,spacerbutton,bullist,numlist,spacerbutton,jiveimage,jivevideo,spacerbutton,jivelink,jiveemoticons,spellchecker";
        delete ret.theme_advanced_buttons2;
        delete ret.theme_advanced_buttons3;
        ret.default_height = 29;
        return ret;
    }else if(id=="widget"){
        var _jive_image_picker_url = false;
        var ret = jive.rte.settings(0);
        ret.content_css = computeStyle(CS_BASE_URL +  "/resources/scripts/tiny_mce3/themes/advanced/skins/default/content.css");
        ret.theme_advanced_statusbar_location = "none";
//        ret.theme_advanced_buttons1 = "bold,italic,underline,strikethrough,spacerbutton,bullist,numlist,spacerbutton,jiveimage,jivevideo,spacerbutton,jivelink,jiveemoticons,spellchecker,html";
//        delete ret.theme_advanced_buttons2;
//        delete ret.theme_advanced_buttons3;
        return ret;
    }else if(id=="wiki"){
        var ret = jive.rte.settings(0);
		ret.theme_advanced_buttons1 = "fontselect, fontsizeselect, removeformat, bullist, numlist, outdent, indent, jivelink,tabletoolbar,extra,spellchecker";
		ret.theme_advanced_buttons2 = "bold,italic,underline,strikethrough,forecolor,jivestyle, justifyleft,justifycenter,justifyright,justifyfull, jiveemoticons, html";
		ret.theme_advanced_buttons3 = "tablecontrols";
        ret.content_css = computeStyle(CS_BASE_URL +  "/resources/scripts/tiny_mce3/themes/advanced/skins/default/wiki.css");
        return ret;
    }else if(id=="blog"){
        var ret = jive.rte.settings(0);
		ret.theme_advanced_buttons1 = "fontselect, fontsizeselect, removeformat, bullist, numlist, outdent, indent, jivelink,tabletoolbar,extra,spellchecker";
		ret.theme_advanced_buttons2 = "bold,italic,underline,strikethrough,forecolor,jivestyle, justifyleft,justifycenter,justifyright,justifyfull, jiveemoticons, html";
		ret.theme_advanced_buttons3 = "tablecontrols";
        ret.content_css = computeStyle(CS_BASE_URL +  "/resources/scripts/tiny_mce3/themes/advanced/skins/default/blog.css");
        return ret;
    }else if(id=="thread"){
        var ret = jive.rte.settings(0);
        ret.content_css = computeStyle(CS_BASE_URL +  "/resources/scripts/tiny_mce3/themes/advanced/skins/default/thread.css");
			ret.theme_advanced_buttons1 = "fontselect, fontsizeselect, removeformat, bullist, numlist, outdent, indent, jivelink,tabletoolbar,extra,spellchecker";
			ret.theme_advanced_buttons2 = "bold,italic,underline,strikethrough,forecolor,jivestyle, justifyleft,justifycenter,justifyright,justifyfull, jiveemoticons";
			ret.theme_advanced_buttons3 = "tablecontrols";
        if(_jive_gui_quote_text && _jive_gui_quote_text.length > 0){
            //ret.theme_advanced_buttons1 = "fontselect, fontsizeselect, removeformat, magicspacer, spacerbutton,bullist, numlist, outdent, indent, spacerbutton,jivevideo,spacerbutton,jivelink,tabletoolbar,extra,jivequote,spellchecker,html";
			ret.theme_advanced_buttons1 = "fontselect, fontsizeselect, removeformat, bullist, numlist, outdent, indent, jivelink,tabletoolbar,extra,spellchecker";
			ret.theme_advanced_buttons2 = "bold,italic,underline,strikethrough,forecolor,jivestyle, justifyleft,justifycenter,justifyright,justifyfull, jiveemoticons";
			ret.theme_advanced_buttons3 = "tablecontrols";
        }
        return ret;
    }else if(id==0){
        return {
        keep_values : true,
        convert_urls : false,
        default_height : 58,
        //theme_advanced_buttons1 : "fontselect, fontsizeselect, removeformat, magicspacer, spacerbutton,bullist, numlist, outdent, indent, spacerbutton,jivevideo,spacerbutton,jivelink,tabletoolbar,extra,spellchecker,html",
        //theme_advanced_buttons2 : "bold,italic,underline,strikethrough,forecolor,jivestyle, magicspacer, spacerbutton, justifyleft,justifycenter,justifyright,justifyfull, spacerbutton,jiveimage,spacerbutton,jiveemoticons, jivemacros ",
        theme_advanced_buttons1 : "fontselect, fontsizeselect, removeformat, bullist, numlist, outdent, indent, jivelink,tabletoolbar,extra,spellchecker",
        theme_advanced_buttons2 : "bold,italic,underline,strikethrough,forecolor,jivestyle, justifyleft,justifycenter,justifyright,justifyfull, jiveemoticons, html",
        theme_advanced_buttons3 : "tablecontrols",
        fix_list_elements : false,
        save_callback : "RawHTMLSaveFunction",
        convert_fonts_to_spans : true,
        font_size_style_values : "8pt,10pt,12pt,14pt,18pt,24pt,36pt",
        strict_loading_mode : true,
        body_class : "jive-widget-formattedtext",
        theme : "advanced",
        // jivemacros must be at the end, b/c it's context menu must override all other plugins
        plugins : "jivebuttons,jiveemoticons,jivestyle,jivelink,jivequote,jivevideo,jiveimage,alignment,safari,spellchecker,html,style,jivelists,table,save,advimage,advlink,iespell,inlinepopups,media,contextmenu,tabletoolbar,jivemacros,jiveutil",
        doctype : '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
        theme_advanced_toolbar_location : "top",
        theme_advanced_toolbar_align : "left",
        theme_advanced_statusbar_location : "bottom",
        content_css : CS_BASE_URL +  "/resources/scripts/tiny_mce3/themes/advanced/skins/default/wiki.css",
        theme_advanced_resize_horizontal : false,
        theme_advanced_resizing : true,
        apply_source_formatting : true,
        spellchecker_languages : SPELL_LANGS,
        spellchecker_rpc_url : CS_BASE_URL + "/spellcheck.jspa",
        jive_image_picker_url: _jive_image_picker_url,
        jive_video_picker_url: _jive_image_picker_url,
        relative_urls : false, // this prevents TinyMCE from converting absolute URLs to relative one's in the editor
        valid_elements : ""
                    +"a[accesskey|charset|class|coords|dir<ltr?rtl|href|hreflang|id|lang|name"
                      +"|onblur|onclick|ondblclick|onfocus|onkeydown|onkeypress|onkeyup"
                      +"|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|rel|rev"
                      +"|shape<circle?default?poly?rect|style|tabindex|title|target|type|jivemacro|_.*],"
                    +"abbr[class|dir<ltr?rtl|id|lang|onclick|ondblclick|onkeydown|onkeypress"
                      +"|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|style"
                      +"|title],"
                    +"acronym[class|dir<ltr?rtl|id|id|lang|onclick|ondblclick|onkeydown|onkeypress"
                      +"|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|style"
                      +"|title],"
                    +"address[class|align|dir<ltr?rtl|id|lang|onclick|ondblclick|onkeydown"
                      +"|onkeypress|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover"
                      +"|onmouseup|style|title],"
                    +"applet[align<bottom?left?middle?right?top|alt|archive|class|code|codebase"
                      +"|height|hspace|id|name|object|style|title|vspace|width],"
                    +"area[accesskey|alt|class|coords|dir<ltr?rtl|href|id|lang|nohref<nohref"
                      +"|onblur|onclick|ondblclick|onfocus|onkeydown|onkeypress|onkeyup"
                      +"|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup"
                      +"|shape<circle?default?poly?rect|style|tabindex|title|target],"
                    +"base[href|target],"
                    +"basefont[color|face|id|size],"
                    +"bdo[class|dir<ltr?rtl|id|lang|style|title],"
                    +"big[class|dir<ltr?rtl|id|lang|onclick|ondblclick|onkeydown|onkeypress"
                      +"|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|style"
                      +"|title],"
                    +"blockquote[cite|class|dir<ltr?rtl|id|lang|onclick|ondblclick"
                      +"|onkeydown|onkeypress|onkeyup|onmousedown|onmousemove|onmouseout"
                      +"|onmouseover|onmouseup|style|title],"
                    +"body[alink|background|bgcolor|class|dir<ltr?rtl|id|lang|link|onclick"
                      +"|ondblclick|onkeydown|onkeypress|onkeyup|onload|onmousedown|onmousemove"
                      +"|onmouseout|onmouseover|onmouseup|onunload|style|title|text|vlink],"
                    +"br[class|clear<all?left?none?right|id|style|title],"
                    +"button[accesskey|class|dir<ltr?rtl|disabled<disabled|id|lang|name|onblur"
                      +"|onclick|ondblclick|onfocus|onkeydown|onkeypress|onkeyup|onmousedown"
                      +"|onmousemove|onmouseout|onmouseover|onmouseup|style|tabindex|title|type"
                      +"|value],"
                    +"caption[align<bottom?left?right?top|class|dir<ltr?rtl|id|lang|onclick"
                      +"|ondblclick|onkeydown|onkeypress|onkeyup|onmousedown|onmousemove"
                      +"|onmouseout|onmouseover|onmouseup|style|title],"
                    +"center[class|dir<ltr?rtl|id|lang|onclick|ondblclick|onkeydown|onkeypress"
                      +"|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|style"
                      +"|title],"
                    +"cite[class|dir<ltr?rtl|id|lang|onclick|ondblclick|onkeydown|onkeypress"
                      +"|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|style"
                      +"|title],"
                    +"code[class|dir<ltr?rtl|id|lang|onclick|ondblclick|onkeydown|onkeypress"
                      +"|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|style"
                      +"|title],"
                    +"col[align<center?char?justify?left?right|char|charoff|class|dir<ltr?rtl|id"
                      +"|lang|onclick|ondblclick|onkeydown|onkeypress|onkeyup|onmousedown"
                      +"|onmousemove|onmouseout|onmouseover|onmouseup|span|style|title"
                      +"|valign<baseline?bottom?middle?top|width],"
                    +"colgroup[align<center?char?justify?left?right|char|charoff|class|dir<ltr?rtl"
                      +"|id|lang|onclick|ondblclick|onkeydown|onkeypress|onkeyup|onmousedown"
                      +"|onmousemove|onmouseout|onmouseover|onmouseup|span|style|title"
                      +"|valign<baseline?bottom?middle?top|width],"
                    +"dd[class|dir<ltr?rtl|id|lang|onclick|ondblclick|onkeydown|onkeypress|onkeyup"
                      +"|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|style|title],"
                    +"del[cite|class|datetime|dir<ltr?rtl|id|lang|onclick|ondblclick|onkeydown"
                      +"|onkeypress|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover"
                      +"|onmouseup|style|title],"
                    +"dfn[class|dir<ltr?rtl|id|lang|onclick|ondblclick|onkeydown|onkeypress"
                      +"|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|style"
                      +"|title],"
                    +"dir[class|compact<compact|dir<ltr?rtl|id|lang|onclick|ondblclick|onkeydown"
                      +"|onkeypress|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover"
                      +"|onmouseup|style|title],"
                    +"div[align<center?justify?left?right|class|dir<ltr?rtl|id|lang|onclick"
                      +"|ondblclick|onkeydown|onkeypress|onkeyup|onmousedown|onmousemove"
                      +"|onmouseout|onmouseover|onmouseup|style|title],"
                    +"dl[class|compact<compact|dir<ltr?rtl|id|lang|onclick|ondblclick|onkeydown"
                      +"|onkeypress|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover"
                      +"|onmouseup|style|title],"
                    +"dt[class|dir<ltr?rtl|id|lang|onclick|ondblclick|onkeydown|onkeypress|onkeyup"
                      +"|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|style|title],"
                    +"em/i[class|dir<ltr?rtl|id|lang|onclick|ondblclick|onkeydown|onkeypress"
                      +"|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|style"
                      +"|title],"
                    +"embed[width|height|src|pluginspage|name|swliveconnect|play<true?false|loop<true?false"
                      + "|menu<true?false|quality<low?autolow?autohigh?high?medium?high?best"
                      + "|scale<default?exact?noorder|salign<l?t?r?b?tl?tr?bl?br|wmode<window?opaque?transparent"
                      + "|bgcolor|base|flashvars|type|allowfullscreen],"
                    +"fieldset[class|dir<ltr?rtl|id|lang|onclick|ondblclick|onkeydown|onkeypress"
                      +"|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|style"
                      +"|title],"
                    +"font[class|color|dir<ltr?rtl|face|id|lang|size|style|title],"
                    +"form[accept|accept-charset|action|class|dir<ltr?rtl|enctype|id|lang"
                      +"|method<get?post|name|onclick|ondblclick|onkeydown|onkeypress|onkeyup"
                      +"|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|onreset|onsubmit"
                      +"|style|title|target],"
                    +"frame[class|frameborder|id|longdesc|marginheight|marginwidth|name"
                      +"|noresize<noresize|scrolling<auto?no?yes|src|style|title],"
                    +"frameset[class|cols|id|onload|onunload|rows|style|title],"
                    +"h1[align<center?justify?left?right|class|dir<ltr?rtl|id|lang|onclick"
                      +"|ondblclick|onkeydown|onkeypress|onkeyup|onmousedown|onmousemove"
                      +"|onmouseout|onmouseover|onmouseup|style|title],"
                    +"h2[align<center?justify?left?right|class|dir<ltr?rtl|id|lang|onclick"
                      +"|ondblclick|onkeydown|onkeypress|onkeyup|onmousedown|onmousemove"
                      +"|onmouseout|onmouseover|onmouseup|style|title],"
                    +"h3[align<center?justify?left?right|class|dir<ltr?rtl|id|lang|onclick"
                      +"|ondblclick|onkeydown|onkeypress|onkeyup|onmousedown|onmousemove"
                      +"|onmouseout|onmouseover|onmouseup|style|title],"
                    +"h4[align<center?justify?left?right|class|dir<ltr?rtl|id|lang|onclick"
                      +"|ondblclick|onkeydown|onkeypress|onkeyup|onmousedown|onmousemove"
                      +"|onmouseout|onmouseover|onmouseup|style|title],"
                    +"h5[align<center?justify?left?right|class|dir<ltr?rtl|id|lang|onclick"
                      +"|ondblclick|onkeydown|onkeypress|onkeyup|onmousedown|onmousemove"
                      +"|onmouseout|onmouseover|onmouseup|style|title],"
                    +"h6[align<center?justify?left?right|class|dir<ltr?rtl|id|lang|onclick"
                      +"|ondblclick|onkeydown|onkeypress|onkeyup|onmousedown|onmousemove"
                      +"|onmouseout|onmouseover|onmouseup|style|title],"
                    +"head[dir<ltr?rtl|lang|profile],"
                    +"hr[align<center?left?right|class|dir<ltr?rtl|id|lang|noshade<noshade|onclick"
                      +"|ondblclick|onkeydown|onkeypress|onkeyup|onmousedown|onmousemove"
                      +"|onmouseout|onmouseover|onmouseup|size|style|title|width],"
                    +"html[dir<ltr?rtl|lang|version],"
                    +"iframe[align<bottom?left?middle?right?top|class|frameborder|height|id"
                      +"|longdesc|marginheight|marginwidth|name|scrolling<auto?no?yes|src|style"
                      +"|title|width],"
                    +"img[align<bottom?left?middle?right?top|alt|border|class|dir<ltr?rtl|height"
                      +"|hspace|id|ismap<ismap|lang|longdesc|name|onclick|ondblclick|onkeydown"
                      +"|onkeypress|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover"
                      +"|onmouseup|src|style|title|usemap|vspace|width|jivemacro|_.*|param_.*],"
                    +"input[accept|accesskey|align<bottom?left?middle?right?top|alt"
                      +"|checked<checked|class|dir<ltr?rtl|disabled<disabled|id|ismap<ismap|lang"
                      +"|maxlength|name|onblur|onclick|ondblclick|onfocus|onkeydown|onkeypress"
                      +"|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|onselect"
                      +"|readonly<readonly|size|src|style|tabindex|title"
                      +"|type<button?checkbox?file?hidden?image?password?radio?reset?submit?text"
                      +"|usemap|value],"
                    +"ins[cite|class|datetime|dir<ltr?rtl|id|lang|onclick|ondblclick|onkeydown"
                      +"|onkeypress|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover"
                      +"|onmouseup|style|title],"
                    +"isindex[class|dir<ltr?rtl|id|lang|prompt|style|title],"
                    +"kbd[class|dir<ltr?rtl|id|lang|onclick|ondblclick|onkeydown|onkeypress"
                      +"|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|style"
                      +"|title],"
                    +"label[accesskey|class|dir<ltr?rtl|for|id|lang|onblur|onclick|ondblclick"
                      +"|onfocus|onkeydown|onkeypress|onkeyup|onmousedown|onmousemove|onmouseout"
                      +"|onmouseover|onmouseup|style|title],"
                    +"legend[align<bottom?left?right?top|accesskey|class|dir<ltr?rtl|id|lang"
                      +"|onclick|ondblclick|onkeydown|onkeypress|onkeyup|onmousedown|onmousemove"
                      +"|onmouseout|onmouseover|onmouseup|style|title],"
                    +"li[class|dir<ltr?rtl|id|lang|onclick|ondblclick|onkeydown|onkeypress|onkeyup"
                      +"|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|style|title|type"
                      +"|value],"
                    +"link[charset|class|dir<ltr?rtl|href|hreflang|id|lang|media|onclick"
                      +"|ondblclick|onkeydown|onkeypress|onkeyup|onmousedown|onmousemove"
                      +"|onmouseout|onmouseover|onmouseup|rel|rev|style|title|target|type],"
                    +"map[class|dir<ltr?rtl|id|lang|name|onclick|ondblclick|onkeydown|onkeypress"
                      +"|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|style"
                      +"|title],"
                    +"menu[class|compact<compact|dir<ltr?rtl|id|lang|onclick|ondblclick|onkeydown"
                      +"|onkeypress|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover"
                      +"|onmouseup|style|title],"
                    +"meta[content|dir<ltr?rtl|http-equiv|lang|name|scheme],"
                    +"noframes[class|dir<ltr?rtl|id|lang|onclick|ondblclick|onkeydown|onkeypress"
                      +"|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|style"
                      +"|title],"
                    +"noscript[class|dir<ltr?rtl|id|lang|style|title],"
                    +"object[align<bottom?left?middle?right?top|archive|border|class|classid"
                      +"|codebase|codetype|data|declare|dir<ltr?rtl|height|hspace|id|lang|name"
                      +"|onclick|ondblclick|onkeydown|onkeypress|onkeyup|onmousedown|onmousemove"
                      +"|onmouseout|onmouseover|onmouseup|standby|style|tabindex|title|type|usemap"
                      +"|vspace|width],"
                    +"ol[class|compact<compact|dir<ltr?rtl|id|lang|onclick|ondblclick|onkeydown"
                      +"|onkeypress|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover"
                      +"|onmouseup|start|style|title|type],"
                    +"optgroup[class|dir<ltr?rtl|disabled<disabled|id|label|lang|onclick"
                      +"|ondblclick|onkeydown|onkeypress|onkeyup|onmousedown|onmousemove"
                      +"|onmouseout|onmouseover|onmouseup|style|title],"
                    +"option[class|dir<ltr?rtl|disabled<disabled|id|label|lang|onclick|ondblclick"
                      +"|onkeydown|onkeypress|onkeyup|onmousedown|onmousemove|onmouseout"
                      +"|onmouseover|onmouseup|selected<selected|style|title|value],"
                    +"p[align<center?justify?left?right|class|dir<ltr?rtl|id|lang|onclick"
                      +"|ondblclick|onkeydown|onkeypress|onkeyup|onmousedown|onmousemove"
                      +"|onmouseout|onmouseover|onmouseup|style|title],"
                    +"param[id|name|type|value|valuetype<DATA?OBJECT?REF],"
                    +"pre/listing/plaintext/xmp[align|class|dir<ltr?rtl|id|lang|onclick|ondblclick"
                      +"|onkeydown|onkeypress|onkeyup|onmousedown|onmousemove|onmouseout"
                      +"|onmouseover|onmouseup|style|title|width|jivemacro|_.*],"
                    +"q[cite|class|dir<ltr?rtl|id|lang|onclick|ondblclick|onkeydown|onkeypress"
                      +"|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|style"
                      +"|title],"
                    +"s[class|dir<ltr?rtl|id|lang|onclick|ondblclick|onkeydown|onkeypress|onkeyup"
                      +"|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|style|title],"
                    +"samp[class|dir<ltr?rtl|id|lang|onclick|ondblclick|onkeydown|onkeypress"
                      +"|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|style"
                      +"|title],"
                    +"script[charset|defer|language|src|type],"
                    +"select[class|dir<ltr?rtl|disabled<disabled|id|lang|multiple<multiple|name"
                      +"|onblur|onchange|onclick|ondblclick|onfocus|onkeydown|onkeypress|onkeyup"
                      +"|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|size|style"
                      +"|tabindex|title],"
                    +"small[class|dir<ltr?rtl|id|lang|onclick|ondblclick|onkeydown|onkeypress"
                      +"|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|style"
                      +"|title],"
                    +"span[align<center?justify?left?right|class|dir<ltr?rtl|id|lang|onclick|ondblclick|onkeydown"
                      +"|onkeypress|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover"
                      +"|onmouseup|style|title|jivemacro|_.*],"
                    +"strike[class|class|dir<ltr?rtl|id|lang|onclick|ondblclick|onkeydown"
                      +"|onkeypress|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover"
                      +"|onmouseup|style|title],"
                    +"strong/b[class|dir<ltr?rtl|id|lang|onclick|ondblclick|onkeydown|onkeypress"
                      +"|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|style"
                      +"|title],"
                    +"style[dir<ltr?rtl|lang|media|title|type],"
                    +"sub[class|dir<ltr?rtl|id|lang|onclick|ondblclick|onkeydown|onkeypress"
                      +"|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|style"
                      +"|title],"
                    +"sup[class|dir<ltr?rtl|id|lang|onclick|ondblclick|onkeydown|onkeypress"
                      +"|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|style"
                      +"|title],"
                    +"table[align<center?left?right|bgcolor|border|cellpadding|cellspacing|class"
                      +"|dir<ltr?rtl|frame|height|id|lang|onclick|ondblclick|onkeydown|onkeypress"
                      +"|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|rules"
                      +"|style|summary|title|width],"
                    +"tbody[align<center?char?justify?left?right|char|class|charoff|dir<ltr?rtl|id"
                      +"|lang|onclick|ondblclick|onkeydown|onkeypress|onkeyup|onmousedown"
                      +"|onmousemove|onmouseout|onmouseover|onmouseup|style|title"
                      +"|valign<baseline?bottom?middle?top],"
                    +"td[abbr|align<center?char?justify?left?right|axis|bgcolor|char|charoff|class"
                      +"|colspan|dir<ltr?rtl|headers|height|id|lang|nowrap<nowrap|onclick"
                      +"|ondblclick|onkeydown|onkeypress|onkeyup|onmousedown|onmousemove"
                      +"|onmouseout|onmouseover|onmouseup|rowspan|scope<col?colgroup?row?rowgroup"
                      +"|style|title|valign<baseline?bottom?middle?top|width],"
                    +"textarea[accesskey|class|cols|dir<ltr?rtl|disabled<disabled|id|lang|name"
                      +"|onblur|onclick|ondblclick|onfocus|onkeydown|onkeypress|onkeyup"
                      +"|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|onselect"
                      +"|readonly<readonly|rows|style|tabindex|title],"
                    +"tfoot[align<center?char?justify?left?right|char|charoff|class|dir<ltr?rtl|id"
                      +"|lang|onclick|ondblclick|onkeydown|onkeypress|onkeyup|onmousedown"
                      +"|onmousemove|onmouseout|onmouseover|onmouseup|style|title"
                      +"|valign<baseline?bottom?middle?top],"
                    +"th[abbr|align<center?char?justify?left?right|axis|bgcolor|char|charoff|class"
                      +"|colspan|dir<ltr?rtl|headers|height|id|lang|nowrap<nowrap|onclick"
                      +"|ondblclick|onkeydown|onkeypress|onkeyup|onmousedown|onmousemove"
                      +"|onmouseout|onmouseover|onmouseup|rowspan|scope<col?colgroup?row?rowgroup"
                      +"|style|title|valign<baseline?bottom?middle?top|width],"
                    +"thead[align<center?char?justify?left?right|char|charoff|class|dir<ltr?rtl|id"
                      +"|lang|onclick|ondblclick|onkeydown|onkeypress|onkeyup|onmousedown"
                      +"|onmousemove|onmouseout|onmouseover|onmouseup|style|title"
                      +"|valign<baseline?bottom?middle?top],"
                    +"title[dir<ltr?rtl|lang],"
                    +"tr[abbr|align<center?char?justify?left?right|bgcolor|char|charoff|class"
                      +"|rowspan|dir<ltr?rtl|id|lang|onclick|ondblclick|onkeydown|onkeypress"
                      +"|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|style"
                      +"|title|valign<baseline?bottom?middle?top],"
                    +"tt[class|dir<ltr?rtl|id|lang|onclick|ondblclick|onkeydown|onkeypress|onkeyup"
                      +"|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|style|title],"
                    +"u[class|dir<ltr?rtl|id|lang|onclick|ondblclick|onkeydown|onkeypress|onkeyup"
                      +"|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|style|title],"
                    +"ul[class|compact<compact|dir<ltr?rtl|id|lang|onclick|ondblclick|onkeydown"
                      +"|onkeypress|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover"
                      +"|onmouseup|style|title|type],"
                    +"var[class|dir<ltr?rtl|id|lang|onclick|ondblclick|onkeydown|onkeypress"
                      +"|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|style"
                      +"|title]"

        };
    }
}


/**
 * a paramter set for macros
 */
jive.rte.ParamSet = function(){

    var that = this;
    this.name = "";
    this.deleteAll = false;
    this.params = new Array();

    this.addParam = function(n, v){
        that.params.push({ name: n, value : v})
    }
    
}

/**
 * a generic Rich Text Editor used in ClearSpace
 * @param domID: the id of the textarea to replace with a RTE
 */
jive.rte.RTE = function(control, domID, settings_id){
    var that = this;
    var textonly = false;
    var tinyText = $(domID);
    var textbox = document.createElement('TEXTAREA');
    textbox.style.display = "none";
    tinyText.parentNode.insertBefore(textbox, tinyText);

    if(!$def(settings_id)){
        settings_id = 0;
    }

    var settings = jive.rte.settings(settings_id);
    settings.mode = "exact";
    settings.elements = domID;
    settings.images_enabled = window._images_enabled;
    try{
        if(!settings.jive_image_picker_url && _jive_image_picker_url){
            settings.jive_image_picker_url = _jive_image_picker_url;
        }
    }catch(e){ }
    if(typeof(tinyMCE) == "undefined"){
        // safari 2!
        textonly = true;
        textbox.style.display = "none";
        tinyText.style.display = "block";
        tinyText.style.height = "200px";
    }else{
        try{
        tinyMCE.init(settings);
        }catch(e){
            textonly = true;
            textbox.style.display = "none";
            tinyText.style.display = "block";
            tinyText.style.height = "200px";
        }
    }

    this.isTextOnly = function(){
        return textonly;
    }


    var my_editor = null;
    var ignore_change = true;

    // true if the spell checker is running
    var is_spelling = false;

    /**
     * returns the tinymce editor
     *
     * this function is private for a reason, and
     * the tinymce editor shouldn't be exposed outside
     * of this class.
     */
    var ignoreResize = true;

    function getEditor(){
        if(!that.isTextOnly() && my_editor == null){
            my_editor = tinyMCE.getInstanceById(domID);
            if(!$def(my_editor) || my_editor == null){
                return null;
            }
            my_editor.getContainer().childNodes[0].style.width = "";
            my_editor.onKeyUp.add(function(ed, e){ that.notifyOnKeyUp(e.keyCode); });
            my_editor.onChange.add(function(ed, e){ that.notifyOnChange(e.keyCode); });
            my_editor.onInit.add(function(that){ return function(ed, e){
                if(ed.id == domID) that.notifyInitFinished();
            }}(that));
            my_editor.onBeginSpelling.add( function(){ is_spelling = true; });
            my_editor.onEndSpelling.add(function(){ is_spelling = false; });
            my_editor.theme.onResize.add(function(){
                if(ignoreResize){
                    ignoreResize = false;
                    that.resizeTo(that.getHeight()+that.getToolbarHeight()+1);
                    my_editor.theme.onResize.dispatch();
                }else{
                    ignoreResize = true;
                    that.notifyResized();
                }
            });
            my_editor.plugins.html.registerToggleFunction(that.toggleEditorMode);
            //
            // sometimes the onInit() event doesn't fire from tinymce,
            // so this is our backup
            window.setTimeout(function() {
                that.notifyInitFinished();
            }, 5000);


            if(window.autoSave){
                var list = {
                    navigateAway : function(){
                        if(window && window.autoSave && that.restoreNavigateAway){
                            window.autoSave.confirmation = that.restoreNavigateAway;
                            that.restoreNavigateAway = false;
                        }
                    }
                }
                window.autoSave.addListener(list);
            }
        }

        return my_editor;
    }

    this.isSpellChecking = function(){
        return is_spelling;
    }

    this.toggleSpellChecker = function(){
        getEditor().execCommand("mceSpellCheck");
    }

    this.closeAllDialogs = function(){
        var windows = getEditor().windowManager.windows;
        var keys = Object.keys(windows);
        for(var i=0;i<keys.length;i++){
            getEditor().windowManager.close(null, windows[keys[i]].id);
        }
        return windows;
    }

    /**
     * adds a macro to the RTE w/ the default paramset
     * @param macro
     * @param paramset a parameter set. these can be created w/ the jive.rte.ParamSet class
     */
    this.addMacro = function(macro, paramset){
        var ed = getEditor();
        var collapsed = ed.selection.isCollapsed();
        var pre = ed.plugins.jivemacros.insertMacro(macro);
        ed.plugins.jivemacros.applyParameterSet(pre, macro, paramset);
        if(collapsed){
            pre.setAttribute("id", "__sel_me__");
            ed.selection.setNode(pre);
            pre = ed.getDoc().getElementById("__sel_me__");
            pre.removeAttribute("id");
        }
        ed.nodeChanged();
        return pre;
    }

    /**
     * destroys an editor
     * @param str
     * @param def
     */
    this.destroy = function(){
        tinyMCE.remove(domID);
        textbox.parentNode.removeChild(textbox);
        getEditor().getContainer().parentNode.removeChild(my_editor.getContainer());
    }

    /**
     * returns the i18n value for the input string, or returns the default if none
     * @param str
     * @param def
     */
    this.getLang = function(str, def){
        return getEditor().getLang(str, def);
    }

    /**
     *  returns true if the editor has been fully loaded
     *  returns false otherwise
     *
     * don't count on any other functions working until this
     * guy returns true...
     */
    this.isReady = function(){
        if(that.isTextOnly()) return true;
        var ed = getEditor();
        return ed != null;
    }


    /**
     * returns the DOM container for this editor
     */
    this.getDOM = function(){
        return getEditor().getContainer();
    }

    /**
     * returns the ID of the DOM that this editor was initialized with
     * @param html
     */
    this.getID = function(){
        return domID;
    }

    /**
     * sets the XHTML data for this editor
     * @param html
     */
    this.setHTML = function(html){
        if(that.isTextOnly()){
            return tinyText.value = html;
        }
        textbox.value = html;
        return getEditor().setContent(html);
    }

    /**
     * returns the XHTML data from this editor
     */
    this.getHTML = function(){
        if(that.isTextOnly()){
            return tinyText.value;
        }
        if(this.isHidden()){
            this.setHTML(textbox.value);
        }
        var body = getEditor().getContent();

        if((this.trim(body).length > 0) && body.indexOf("<body") != 0){
            body = "<body>" + body + "</body>";
        }
        while(body.indexOf("  ") >= 0){
            body = body.replace("  ", "&nbsp; ");
        }
        return body;
    }

    /**
     * returns the trimmed string
     */

    this.trim = function(input){
        var	str = input.replace(/^\s\s*/, '');
        var ws = /\s/;
        var i = str.length;
        while (ws.test(str.charAt(--i)));
        return str.slice(0, i + 1);
    }


    /**
     * shows the editor in the page if it's hidden
     */
    this.show = function(){
        getEditor().show();
        that.notifyShowing();
    }

    /**
     *  returns the window object that this RTE is
     *  inside of
     */
    this.getWindow = function(){
        return getEditor().getWin();
    }

    /**
     * returns the document body of which this
     * RTE is a part
     */
    this.getBody = function(){
        return getEditor().getBody();
    }

    /**
     * focuses the cursor in the RTE
     */
    this.focus = function(){
        try{
            if(this.isHidden()){
                textbox.focus();
            }else{
                getEditor().focus();
            }
        }catch(e){ }
    }

    /**
     * collapse the editor's cursor to a point
     */
    this.collapseSelection = function(){
        var ed = getEditor();
        ed.selection.select(ed.selection.getNode());
        ed.selection.collapse();
    }

    /**
     * hides the RTE from view
     */
    this.hide = function(){
        getEditor().hide();
    }

    /**
     * returns true of the RTE is hidden from view
     */
    this.isHidden = function(){
        return textbox.style.display == "block";
    }

    /**
     * resize the editor to the input height
     * @param height
     */
    this.resizeTo = function(height){
        var contain = getEditor().getContainer();
        var table = contain.firstChild;
        if(table.nodeName.toLowerCase() != "table") table = table.childNodes[0]; // IE 6
        var h = jive.ext.x.xHeight(table.rows[0].cells[0]);
        if(table.rows.length > 2){
            h += jive.ext.x.xHeight(table.rows[table.rows.length-1].cells[0]);
        }
        var box = getEditor().getContentAreaContainer();
        var ifr = getEditor().getContentAreaContainer().firstChild;
        tinymce.DOM.setStyle(ifr, 'height', height-h); // Resize iframe
//        tinymce.DOM.setStyle(box, 'height', height-h); // Resize td around the iframe
        tinymce.DOM.setStyle(table, 'height', height); // Resize table
    }

    /**
     * returns the height in px of the editor
     */
    this.getHeight = function(){
        var ifr = getEditor().getContentAreaContainer().firstChild;
        return jive.ext.x.xHeight(ifr);
    }

    this.getToolbarHeight = function(){
        var contain = getEditor().getContainer();
        var table = contain.firstChild;
        if(table.nodeName.toLowerCase() != "table") table = table.childNodes[0]; // IE 6
        var h = jive.ext.x.xHeight(table.rows[0].cells[0]);
        if(table.rows.length > 2){
            h += jive.ext.x.xHeight(table.rows[table.rows.length-1].cells[0]);
        }
        return h;
    }

    /**
     * toggles the editor between rich and raw html
     */
    this.toggleEditorMode = function(edid){
        if(edid == domID){
            if(window && window.autoSave){
                that.restoreNavigateAway = window.autoSave.confirmation;
                window.autoSave.confirmation = false;
            }
            var tiny = $(domID + '_parent');
            if(that.isHidden()){
                var h = jive.ext.x.xHeight(textbox);
                textbox.style.display = "none";
                that.show();
                that.setHTML(textbox.value);
                that.focus();
                that.resizeTo(h);
                getEditor().plugins.jivemacros.removeDuplicateMacros(getEditor(), getEditor().getBody());
            }else{
                var tinyt = $(domID + '_tbl');
                var h = jive.ext.x.xHeight(tinyt);
                textbox.value = that.getHTML(true);
                that.hide();
                textbox.style.display = "block";
                // never show tinyMCE's text box to the user
                // only show our own text box
                tinyText.style.display = "none";
                jive.ext.x.xHeight(textbox,h);
                that.focus();
            }
            that.notifyDoneTogglingMode();
        }
    }


    /******************************************
     * listener functions
     ******************************************/
    this.addListener = function(list){
        if($def(list.onShow)) list.onShow();
        listeners.push(list);
    }

    var listeners = new Array();
    var listener_actions = new Array();
    /**
     * act must be a thunk (a function without arguments)
     * it will be executed after either
     * notifyLoadFinish or notifyLoadFail
     */
    this.addListenerAction = function(act){
        listener_actions.push(act);
    }

    /**
     * private
     * executes all the listener actions
     */
    this.executeListenerActions = function(){
        while(listener_actions.length > 0){
            listener_actions[0]();
            listener_actions.splice(0,1);
        }
    }

    this.removeListener = function(list){
        for(var i=0;i<listeners.length;i++){
            if(listeners[i] == list){
                listeners.splice(i, 1);
            }
        }
    }

    this.initted = false;
    this.notifyInitFinished = function(){
        if(!that.initted){
            that.initted = true;
            for(var i=0;i<listeners.length;i++){
                listeners[i].initFinished();
            }
            that.executeListenerActions();
        }
    }

    this.notifyOnKeyUp = function(keyCode){
        // the user typed a key!
        for(var i=0;i<listeners.length;i++){
            listeners[i].onKeyUp(keyCode);
        }
        that.executeListenerActions();
    }

    this.notifyOnChange = function(){
        if(ignore_change){
            ignore_change = false;
            return;
        }
        // the user typed a key!
        for(var i=0;i<listeners.length;i++){
            listeners[i].onChange();
        }
        that.executeListenerActions();
    }

    this.notifyResized = function(){
        // the user typed a key!
        for(var i=0;i<listeners.length;i++){
            listeners[i].onResize();
        }
        that.executeListenerActions();
    }

    this.notifyShowing = function(){
        // the user typed a key!
        for(var i=0;i<listeners.length;i++){
            listeners[i].onShow();
        }
        that.executeListenerActions();
    }

    this.notifyDoneTogglingMode = function(){
        // the user typed a key!
        for(var i=0;i<listeners.length;i++){
            listeners[i].doneTogglingMode();
        }
        that.executeListenerActions();
    }
}


/**
 * defines a simple Macro interface to mimic the RenderMacro class on the server
 */
jive.rte.Macro = function(shortname, url, macrotag, settingsHuh, displayHuh, paramSets, params, enabled, button){
    var that = this;

    /**
     * gets the unique name for this macro
     * i.e. "code" or "youtube"
     */
    this.getName = function(){
        return shortname;
    }

    /**
     * gets the optional url for this macro
     */
    this.getUrl = function(){
        return url;
    }

    /**
     * returns true if it should be a button or not
     */
    this.isButton = function(){
        return button;
    }

    this.isEnabled = function(){
        return enabled;
    }

    this.isShowSettings = function(){
        return settingsHuh;
    }

    /**
     * Display in RTE Insert List?
     */
    this.isShowInMacroList = function(){
        return displayHuh;
    }
    
    /**
     * returns true if this macro accepts
     * raw text input, like a code macro,
     * or false if it doesn't, like
     * a youtube macro
     */
    this.getMacroType = function(){
        return macrotag;
    }

    /**
     * returns all param sets for this macro
     */
    this.getParameterSets = function(){
        return paramSets;
    }

    /**
     * returns an array of allowed parameters
     */
    this.getAllowedParameters = function(){
        return params;
    }

    /**
     * update the element's display w/ the latest
     * parameter value.
     */
    this.refresh = function(rte, ele){
        if(ele.getAttribute("jivemacro") == this.getName()){
            // i'm a span or an img
            if(this.getMacroType().toLowerCase() == "inline"){
                var str = ele.getAttribute("_title");
                if($def(str) && str != null && str.length > 0){
                    ele.innerHTML = str;
                    ele.attributes.removeNamedItem("_title");
                }else if(tinyMCE.activeEditor.dom.hasClass(ele, "default_title")){
                    var type = ele.getAttribute("jivemacro");
                    var id = ele.getAttribute("___default_attr");
                    var title = tinyMCE.activeEditor.plugins.jivemacros.getTitleFor(type, id);
                    if(title && ele.innerHTML != title[0]){
                        ele.innerHTML = title[0];
                    }
                    if(ele.innerHTML == ""){
                        ele.innerHTML = "unknown";
                    }
                }
                /*
                while(ele.childNodes.length > 1) ele.removeChild(ele.childNodes[0]);
                if(ele.childNodes.length == 0) ele.appendChild(document.createTextNode(""));
                var params = this.getAllowedParameters();
                for(var i=0;i<params.length;i++){
                    var val = ele.getAttribute("_" + params[i].name);
                    if(val != null && val.length > 0){
                        ele.childNodes[0].nodeValue = val;
//                            ele.appendChild(document.createTextNode(val));
                        return;
                    }
                }
                ele.childNodes[0].nodeValue = rte.getLang("jivemacros.macro." + this.getName(), this.getName());
                */
            }else if(this.getMacroType().toLowerCase() == "image"){
                var src = window.CS_BASE_URL + "/resources/scripts/tiny_mce3/plugins/jiveemoticons/images/spacer.gif";
                ele.setAttribute("src", src);
                ele.setAttribute("mce_src", src);
            }
        }
    }
}

/**
 * since emoticons are encoded in the XHTML as macros,
 * but implemented onthe server as a filter, this class
 * will define the client side "macro" class for emotes,
 * since once is not generated on the fly from the
 * server
 *
 * alternatively, i could have refactored emoticons into
 * a macro from a filter on the server, but don't fix
 * what isn't broken :)
 */
jive.rte.EmoticonMacro = function(){

    var that = this;

    var params = new Array();
    var paramSets = new Array();
    params.push({
        name: "__jive_emoticon_name",
        value: ["happy","laugh","silly","wink","plain","angry","blush","confused","cool","cry","devil","grin","love","mischief","sad","shocked"]
    });

    for(var i=0;i<params[0].value.length;i++){
        paramSets.push({
            name : params[0].value[i],
            deleteAll: true,
            params: [ {
                name: params[0].name,
                value: params[0].value[i]
            }]
        });
    }
    var macro = new jive.rte.Macro("emoticon", "", "img", false, true, paramSets, params, true, false)

    this.getName = macro.getName;

    this.getUrl = macro.getUrl;

    this.isShowInMacroList = macro.isShowInMacroList;

    this.isShowSettings = macro.isShowSettings;

    this.getMacroType = macro.getMacroType;

    this.getParameterSets = macro.getParameterSets;

    this.getAllowedParameters = macro.getAllowedParameters;

    this.refresh = function(rte, ele){
        macro.refresh(rte, ele);
        var grin = ele.getAttribute("_" + params[0].name);
        // also, update the icon
        ele.setAttribute("class","jive_macro jive_emote");
        ele.setAttribute("src", window.CS_BASE_URL + "/images/emoticons/" + grin + ".gif");
        ele.setAttribute("mce_src", window.CS_BASE_URL + "/images/emoticons/" + grin + ".gif");
    }

}
jive.rte.macros.push(new jive.rte.EmoticonMacro());

jive.rte.RTEListener = function(){
    this.onKeyUp = function(key){ }
    this.onChange = function(){ }
    this.onResize = function(){ }
    this.onShow = function(){ }
    this.doneTogglingMode = function(){ }
    this.initFinished = function(){ }
}

/**
 * a task dom, like in day view
 */
jive.gui.CPDOM = function(control, cp, clickFunc, dblClickFunc){
	var that = this;


	//
	// these styles are overridden in month view :(
	//
	var holder = document.createElement('DIV');
	holder.setAttribute("class", "jive-link-checkpoint jiveTT-hover-checkpoint");
	holder.className = "jive-link-checkpoint jiveTT-hover-checkpoint";

	var title = document.createElement('SPAN');

	// title
	var tmp_title = cp.getName();
	if(tmp_title.length == 0) tmp_title = "(no title)";
	var n = document.createTextNode(tmp_title);

	// description
	var desc = document.createElement('SPAN');
	desc.setAttribute("class", "month_day_cell_event_desc");
	desc.className = "month_day_cell_event_desc";
	var tmp_desc = "";
	if(cp.getDescription().unescapeHTML().length > 0){
		tmp_desc = ": " + cp.getDescription().unescapeHTML();
	}
	desc.appendChild(document.createTextNode(tmp_desc));

	title.appendChild(n);
	title.appendChild(desc);
	holder.appendChild(title);

	holder.getCheckPoint = function(cp){ return function(){ return cp; }; }(cp);

	this.lighten = function(){
		holder.setAttribute("class", "month_day_cell_item month_lighten_dom");
		holder.className = "month_day_cell_item month_lighten_dom";
	}

	this.darken = function(){
		holder.setAttribute("class", "month_day_cell_item");
		holder.className = "month_day_cell_item";
	}

	// this is a hook to update the text on this item
	this.refresh = function(){
		// remove the task title and description
		title.removeChild(title.childNodes[1]);
		title.removeChild(title.childNodes[0]);
		// add it back
		title.appendChild(document.createTextNode(cp.getName()));

		var tmp_desc = "";
		if(cp.getDescription().unescapeHTML().length > 0){
			tmp_desc = ": " + cp.getDescription().unescapeHTML();
		}
		desc.removeChild(desc.childNodes[0]);
		desc.appendChild(document.createTextNode(tmp_desc));
		title.appendChild(desc);
	}

	this.showDescription = function(b){
		if(b){
			jive.ext.x.xDisplayInline(desc);
		}else{
			jive.ext.x.xDisplayNone(desc);
		}
	}

	jive.ext.x.xAddEventListener(holder, "click", function(clickFunc, cp, title){
		return function(e){
			clickFunc(cp);
			jive.ext.x.xStopPropagation(e);
			title.setAttribute("class", "month_view_day_task_title");
			title.className = "month_view_day_task_title";
		}
	}(clickFunc, cp, title));
	jive.ext.x.xAddEventListener(holder, "dblclick", function(dblClickFunc, cp, title){
		return function(e){
			dblClickFunc(cp);
			jive.ext.x.xStopPropagation(e);
			title.setAttribute("class", "month_view_day_task_title");
			title.className = "month_view_day_task_title";
		}
	}(dblClickFunc, cp, title));


	this.getDOM = function(){
		return holder;
	}

	this.killYourself = function(){
		cp = null;
        control = null;
    }

	this.getCheckPoint = holder.getCheckPoint;

	/**
	 * will style this dom as highlighted
	 * if b is true
	 * otherwise, it'll style the default style
	 */
	this.setHighlight = function(b){
		if(b){
			holder.setAttribute("class", "month_day_cell_item_highlight");
			holder.className = "month_day_cell_item_highlight";
		}else{
			holder.setAttribute("class", "month_day_cell_item");
			holder.className = "month_day_cell_item";
		}
	}

}

/**
 * a task dom, like in day view
 */
jive.gui.TaskDOM = function(control, ttask, clickFunc, dblClickFunc){
	var that = this;


	//
	// these styles are overridden in month view :(
	//
	var holder = document.createElement('A');
	holder.setAttribute("class", "month_day_cell_item");
	holder.className = "month_day_cell_item";

	var title = document.createElement('SPAN');
	title.setAttribute("class", "month_view_day_task_title");
	title.className = "month_view_day_task_title";

	// title
	var tmp_title = ttask.getSubject();
	if(tmp_title.length == 0) tmp_title = "(no title)";
	var n = document.createTextNode(tmp_title);

	// description
	var desc = document.createElement('SPAN');
	desc.setAttribute("class", "month_day_cell_event_desc");
	desc.className = "month_day_cell_event_desc";
	var tmp_desc = "";
	if(ttask.getDescription().unescapeHTML().length > 0){
		tmp_desc = ": " + ttask.getDescription().unescapeHTML();
	}
	desc.appendChild(document.createTextNode(tmp_desc));

	//
	//
	// different browsers handle the onClick event differently
	// firefox calls the event AFTER the checked status has changed,
	// but safari calls it BEFORE the checked status has changed.
	//
	// we handle this by maintaining 2 .checked properties.
	// the .checked property is managed by teh browser, and tells if teh
	// checkbox is on/off.
	//
	// the .checkedCache is our internal cache for what the last value was.
	//
	// if .checked == .checkedCache when the click function is called,
	// then we're in safari, and need to toggle both .checked and .checkedCache
	//
	// if .checked != .checkedCache when the click function is called,
	// then we're in firefox, and we only need to update the .checkedCache
	//
	//
	var check = document.createElement('INPUT');
	check.setAttribute("type", "checkbox");
	check.type = "checkbox";
	if(ttask.isComplete()){
		check.checked = true;
		check.checkedCache = true;
	}else{
		check.checkedCache = false;
	}

	jive.ext.x.xAddEventListener(check, "click", function(check, ttask){ return function(e){
		try{
			if(check.checkedCache == check.checked){
				check.checkedCache = !check.checkedCache;
				check.checked = !check.checked;
			}else{
				check.checkedCache = !check.checkedCache;
			}

			if(check.checked){
				ttask.setComplete(true);
			}else{
				ttask.setComplete(false);
			}
			ttask.confirm();
			jive.ext.x.xStopPropagation(e);
		}catch(e){
			alert(e);
		}
	}}(check, ttask));

	holder.appendChild(check);
	title.appendChild(n);
	title.appendChild(desc);
	holder.appendChild(title);

	holder.getTask = function(ttask){ return function(){ return ttask; }; }(ttask);

	holder.setDisabled = function(check){ return function(foo){
		check.disabled = foo;
	}}(check);

	holder.isDisabledHuh = function(check){ return function(){
		return check.disabled;
	}}(check);

	holder.setChecked = function(check){ return function(foo){
		check.checked = foo;
	}}(check);
	holder.isCheckedHuh = function(check){ return function(){
		return check.checked;
	}}(check);


	this.lighten = function(){
		holder.setAttribute("class", "month_day_cell_item month_lighten_dom");
		holder.className = "month_day_cell_item month_lighten_dom";
	}

	this.darken = function(){
		holder.setAttribute("class", "month_day_cell_item");
		holder.className = "month_day_cell_item";
	}

	// this is a hook to update the text on this item
	this.refresh = function(){
		// remove the task title and description
		title.removeChild(title.childNodes[1]);
		title.removeChild(title.childNodes[0]);
		// add it back
		title.appendChild(document.createTextNode(ttask.getSubject()));

		var tmp_desc = "";
		if(ttask.getDescription().unescapeHTML().length > 0){
			tmp_desc = ": " + ttask.getDescription().unescapeHTML();
		}
		desc.removeChild(desc.childNodes[0]);
		desc.appendChild(document.createTextNode(tmp_desc));
		title.appendChild(desc);

		// update the checkbox
		if(ttask.isComplete()){
			holder.setChecked(true);
		}else{
			holder.setChecked(false);
		}
	}

	this.showDescription = function(b){
		if(b){
			jive.ext.x.xDisplayInline(desc);
		}else{
			jive.ext.x.xDisplayNone(desc);
		}
	}

	// set default value
	holder.setChecked(ttask.isComplete());



	jive.ext.x.xAddEventListener(holder, "click", function(clickFunc, ttask, title){
		return function(e){
			clickFunc(ttask);
			jive.ext.x.xStopPropagation(e);
			title.setAttribute("class", "month_view_day_task_title");
			title.className = "month_view_day_task_title";
		}
	}(clickFunc, ttask, title));
	jive.ext.x.xAddEventListener(holder, "dblclick", function(dblClickFunc, ttask, title){
		return function(e){
			dblClickFunc(ttask);
			jive.ext.x.xStopPropagation(e);
			title.setAttribute("class", "month_view_day_task_title");
			title.className = "month_view_day_task_title";
		}
	}(dblClickFunc, ttask, title));


	this.getDOM = function(){
		return holder;
	}

	this.killYourself = function(){
		ttask = null;
        control = null;
    }

	this.getTask = holder.getTask;
	this.setDisabled = holder.setDisabled;
	this.isDisabledHuh = holder.isDisabledHuh;
	this.setChecked = holder.setChecked;
	this.isCheckedHuh = holder.isCheckedHuh;

	/**
	 * will style this dom as highlighted
	 * if b is true
	 * otherwise, it'll style the default style
	 */
	this.setHighlight = function(b){
		if(b){
			holder.setAttribute("class", "month_day_cell_item_highlight");
			holder.className = "month_day_cell_item_highlight";
		}else{
			holder.setAttribute("class", "month_day_cell_item");
			holder.className = "month_day_cell_item";
		}
	}

	/**
	 * set the taskdom as disabled if
	 * we don't have write permission to
	 * its calendar
	 */
	function setProject(proj){
		if(proj == null || proj.isEditable()){
            holder.setDisabled(false);
		}else{
            holder.setDisabled(true);
		}
	}

    if(ttask.getProjectID() == 0){
        setProject(null);
    }else{
        var proj = control.getProjectCache().getProject(ttask.getProjectID());
        if($obj(proj) && proj != null){
            setProject(proj);
        }else{
            var list = new jive.model.ProjectCacheListener();
            list.loadProject = function(proj){
                if(proj.getID() == ttask.getProjectID()){
                    setProject(proj);
                    control.getProjectCache().removeListener(this);
                }
            }
            control.getProjectCache().addListener(list);
        }
    }
}

jive.gui.isMonthEventDOM = function(item){
	return $def(item) && $def(item.getEvent);
}
jive.gui.isMonthTaskDOM = function(item){
	return $def(item) && $def(item.getTask);
}


/**
 * a month panel is composed of day cell holders, which in turn hold
 * the actual day cells. each holder holds 4 weeks of day cells. these
 * holders can be added to the beginning/end of the month view.
 *
 * to add the month panel to the UI, call it's getDOM() method
 * which returns a dom object
 */
jive.gui.MonthView = function(control, aurora_gui){


	var that = this;

	var expanded = false;


	var has_add_view = null;

	this.hasAddView = function(){
		if(has_add_view == null){
			has_add_view = $obj(aurora_gui.getView("add_event"));
		}
		return has_add_view;
	}

	/**
	 * holds day cells
	 */
	this.dayCells = new jive.ext.y.HashTable();

	/**
	 * holds the spans that hold the event titles
	 * will return an array for a key
	 * the array will hold all of the title spans for that event
	 *
	 * ie, if an event spans the days, it will have an array of 3 spans
	 * one for each day cell
	 */
	this.eventDOMHolders = new jive.ext.y.HashTable();
	this.taskDOMHolders = new jive.ext.y.HashTable();

	/**
	 * set to true if showMonth() should ignore
	 * optimizing by not showing month if the date is the same
	 * as is currently shown.
	 *
	 * ie, set to true to force showMonth to do something
	 * meaningful
	 */
	var force_month_view = false;

	/**
	 * we're also going to cache
	 * the event and task by its calendar id
	 */
	var taskDOMbyCalHolder = new jive.ext.y.HashTable();
	var eventDOMbyCalHolder = new jive.ext.y.HashTable();

	/**
	 * track which calendar id's we're caching each task/event by
	 */
	var task2cal = new jive.ext.y.HashTable();
	var event2cal = new jive.ext.y.HashTable();

	/**
	 * this is the main panel that holds all the day cell holders
	 */
	var main_panel = document.createElement('DIV');
	main_panel.setAttribute("class", "month_view_holder");
	main_panel.className = "month_view_holder";


	var visi = true;
	this.setItemVisibility = function(b){
		visi = b;
	}
	this.getItemVisibility = function(){
		return visi;
	}

	/**********************************************************************
	***********************************************************************
	***********************************************************************
	**
	** BEGIN VIEW INTERFACE TO AURORA GUI
	**
	***********************************************************************
	***********************************************************************
	***********************************************************************/

	/**
	 * return true if we can print this view, false otherwise
	 */
	this.hasPrintView = function(){
		return true;
	}

	/**
	 * this returns true if week view is the current view, false otherwise
	 */
	this.isExpandedHuh = function(){
		return expanded;
	}

	/**
	 * this function is called when week view comes into view
	 * we have officially 'switched' the view to week view
	 */
	this.expand = function(){
		expanded = true;
		aurora_gui.showArrows();
		jive.ext.x.xDisplayBlock(main_panel);
	}

	/**
	 * this function is called when week view goes out of view
	 * we have officially 'switched out' the view from week view
	 */
	this.collapse = function(){
		expanded = false;
		jive.ext.x.xDisplayNone(main_panel);
	}

	// the function to switch to the previous month
	// (used in the previous button)
	function prevMonthFunc(){
		var d = new Date();
		d.setTime(aurora_gui.getCurrentDate().getTime());
		jive.model.dateMinusMonth(d);
		aurora_gui.setCurrentDate(d);
		aurora_gui.notifyMonthClicked(d);
	}
	// the function to switch to the next month
	// (used in the next button)
	function nextMonthFunc(){
		var d = new Date();
		d.setTime(aurora_gui.getCurrentDate().getTime());
		d.setMonth(d.getMonth()+1);
		while(d.getMonth() > aurora_gui.getCurrentDate().getMonth()+1) jive.model.dateMinusDay(d);
		aurora_gui.setCurrentDate(d);
		aurora_gui.notifyMonthClicked(d);
	}

	/**
	 * return the function to go back a month
	 */
	this.getPrevViewFunc = function(){
		// the function to switch to the previous week
		// (used in the previous button)
		return prevMonthFunc;
	}

	/**
	 * return the function to go forward a month
	 */
	this.getNextViewFunc = function(){
		// the function to switch to the next week
		// (used in the next button)
		return nextMonthFunc;
	}

	/**
	 * when this view is in range, calcualte the min date that should be in range
	 * given month view's getCurrentDate() function
	 */
	this.getMinDate = function(){
		return aurora_gui.getMinDate();
	}

	/**
	 * when this view is in range, calcualte the min date that should be in range
	 * given month view's getCurrentDate() function
	 */
	this.getMaxDate = function(){
		return aurora_gui.getMaxDate();
	}

	/**
	 * returns teh text that should be in month view's header
	 * this is based on month_view's getCurrentDate() function
	 */
	this.getHeaderText = function(){
		var lang = control.getLanguageManager().getActiveLanguage();

		var d = new Date();
		d.setTime(aurora_gui.getCurrentDate().getTime());

		return lang.longMonth(d.getMonth());
	}

	/**
	 * shows the month according to month view's current date
	 */
	this.go = function(d){
		aurora_gui.setCurrentDate(d);
		that.showMonth(aurora_gui.getCurrentDate());
	}

	/**
	 * return the name of this view
	 */
	this.getName = function(){
		return "month";
	}

	/**
	 * return the unique hash for this view
	 */
	this.getHash = function(){
		return "month";
	}


	/**
	 * the language has been updated,
	 */
	this.updateText = function(){
		if(main_panel.childNodes.length > 0){
			var start_on = control.getSettingsManager().getStartWeekOn();
			var lang = control.getLanguageManager().getActiveLanguage();
			for(var i=start_on;i-start_on<7;i++){
				var td = main_panel.childNodes[0].childNodes[0].childNodes[(i-start_on)%7]; // the table cell
				if(td.childNodes.length > 0) td.removeChild(td.childNodes[0]);
				td.appendChild(document.createTextNode(lang.longDay(i%7)));
				td.setAttribute("height", "2");
				td.height = "2";
			}
		}
	}

	/**
	 * return the DOM object that represents this month view
	 */
	this.getDOM = function(){
		return main_panel;
	}


	/**
	 * this flushes a calendar entirely. it removes it from teh views,
	 * and also removes any DOM's we have cached for this calendar's
	 * events
	 */
	var filter_time;
	function filterNode(node, str){
		var title;
		var desc;
		var cal_id;
		if($def(node.getEvent)){
			var event = node.getEvent();
			title = event.getSubject().toLowerCase();
			desc = event.getDescription().toLowerCase();
			cal_id = event.getCalendarId();
		}else
		if($def(node.getTask)){
			var task = node.getTask();
			title = task.getSubject().toLowerCase();
			desc = task.getDescription().toLowerCase();
			cal_id = task.getProjectID();
		}
		if(control.isCalendarVisibleHuh(cal_id)){
			if(str.length == 0  || str.length > 0 && (title.indexOf(str) >= 0 || desc.indexOf(str) >= 0)){
//				alert("showing: " + cell.childNodes[i].innerText);
				node.darken();
			}else{
//				alert("hiding: " + cell.childNodes[i].innerText);
				node.lighten();
			}
		}
	}
	var last_filter = "";
	var last_filter_month = (new Date()).getMonth();
	this.filter = function(str){
		//
		// don't bother filtering if it's
		// the same thing we did last time
		if(last_filter != str || last_filter_month != aurora_gui.getCurrentDate().getMonth()){
			last_filter = str;
			last_filter_month = aurora_gui.getCurrentDate().getMonth()
			if(that.isExpandedHuh()){
				filter_time = "" + (new Date()).getTime() + "" + Math.random();
				var my_time = filter_time;
				var dt = new Date();
				var min = aurora_gui.getMinDate();
				var max = aurora_gui.getMaxDate();
				dt.setTime(min.getTime());
				str = str.toLowerCase();
				while(my_time == filter_time && (dt.getTime() < max.getTime() + 24*60*60*1000)){
					var cell = getDayCell(dt);
					for(var i=1; i < cell.childNodes.length; i++){
						filterNode(cell.childNodes[i], str);
					}
					dt.setTime(dt.getTime() + 24*60*60*1000);
				}
			}
		}
	}


	/**
	 * add (or refresh) an event to display on this month view
	 * i need to update this function. its not handling teh drag listeners
	 * correctly. i need to remove old listeners before i add new ones...
	 */
	this.addEvent = function(tevent){
		try{
			var settings = control.getSettingsManager();

			var e_obj = getDOMArray(tevent);



			var iter = new Date();
			if(tevent.isAllDay()){
				iter.setTime(tevent.getStart().getTime());
			}else{
				iter.setTime(settings.adjustDate(tevent.getStart()).getTime());
			}
			var i=0;
			var end_iter = new Date();
			if(tevent.isAllDay()){
				end_iter.setTime(tevent.getEnd().getTime());
			}else{
				end_iter.setTime(settings.adjustDate(tevent.getEnd()).getTime());
			}

			if(jive.model.dateLT(iter, control.getEventCache().getMinTime())){
				// iter is smaller, so change iter to min date
				iter.setTime(control.getEventCache().getMinTime().getTime());
			}
			if(jive.model.dateGT(end_iter, control.getEventCache().getMaxTime())){
				// end_iter is too large, so change end_iter to max date
				end_iter.setTime(control.getEventCache().getMaxTime().getTime());
			}


			while(jive.model.dateLTEQ(iter, end_iter)){
				var d = getDayCell(iter);
				// update the listener
				if($def(control.getDragManager)){
					var dm = control.getDragManager();
					var dragDate = new Date();
					dragDate.setTime(iter.getTime());
					var dlist = new jive.gui.CellDragListener(control, tevent, dragDate, control.notifyStopDrag, control.notifyDragging);
					if($obj(e_obj[i].monthViewDList) && e_obj[i].monthViewDList != null){
						dm.removeDragListener(e_obj[i], e_obj[i].monthViewDList);
					}
					e_obj[i].monthViewDList = dlist;
					dm.enableDrag(e_obj[i]);
					dm.addDragListener(e_obj[i], dlist);
				}
				/**
				 * hide the event if the calendar is hidden
				 */
				if(!control.isCalendarVisibleHuh(tevent.getCalendarId())){
					jive.ext.x.xDisplayNone(e_obj[i]);
				}else{
					jive.ext.x.xDisplayBlock(e_obj[i]);
				}
				var t = aurora_gui.getFilterText();
				filterNode(e_obj[i], t)
				d.appendEventDOM(e_obj[i]);
				// update our parent
				e_obj[i].myParent = d;

				i++;
				iter.setDate(iter.getDate() + 1);

			}
		}catch(e){
			alert("error adding event to month view: " + e);
		}
	}


	/**
	 * add a task to display on this month view
	 */
	this.addTask = function(ttask){
		try{

			var settings = control.getSettingsManager();

			var e_obj = getTaskDOM(ttask);

			var d = getDayCell(ttask.getDueDate());
			d.appendTaskDOM(e_obj);
			// update our parent
			e_obj.myParent = d;

			// update text/checkbox
			e_obj.refresh();

			// update the drag listener
			if($def(control.getDragManager)){
				var dm = control.getDragManager();
				var dragDate = new Date();
				dragDate.setTime(ttask.getDueDate());
				var dlist = new jive.gui.CellDragListener(control, ttask, dragDate, control.notifyStopDrag, control.notifyDragging);
				if($obj(e_obj.monthViewDList) && e_obj.monthViewDList != null){
					dm.removeDragListener(e_obj, e_obj.monthViewDList);
				}
				e_obj.monthViewDList = dlist;
				dm.enableDrag(e_obj);
				dm.addDragListener(e_obj, dlist);
			}


			/**
			 * hide the event if the calendar is hidden
			 */
			if(!control.isCalendarVisibleHuh(ttask.getProjectID())){
				jive.ext.x.xDisplayNone(e_obj);
			}else{
				jive.ext.x.xDisplayBlock(e_obj);
			}
		}catch(e){
			alert(e);
		}
	}

	/**
	 * removes an event from the month view
	 *
	 * this removes any drag listeners from span elements
	 * for that event, and disables the drag
	 */
	this.removeEvent = function(tevent){
		try{
			var settings = control.getSettingsManager();
			// i have completed the drag
			// so remove the listener from
			// all of this events listings in month view
			//
			// i'll be added again by the day cell...
			var arr = getDOMArray(tevent, true);
			// var arr = this.eventDOMHolders.get(event.getId());
			if(jive.ext.y.yArr(arr)){
				for(var j=0;j<arr.length;j++){
					if($def(control.getDragManager())){
						/**
						 * if we don't remove the drag listener
						 * then the event will always think it
						 * started dragging on the wrong day
						 * (the same day really, the day the event
						 *  was on when the page loaded)
						 */
						var dm = control.getDragManager();
						dm.removeDragListener(arr[j], arr[j].monthViewDList);
						dm.disableDrag(arr[j]);
					}
					if($obj(jive.ext.x.xParent(arr[j])) && jive.ext.x.xParent(arr[j]) != null){
						jive.ext.x.xParent(arr[j]).removeChild(arr[j]);
						arr[j].myParent = null;
					}else if($obj(arr[j].myParent) && arr[j].myParent != null){
						arr[j].myParent.removeChild(arr[j]);
						arr[j].myParent = null;
					}
					arr[j].killYourself();
				}
			}
		}catch(e){
			alert("error removing event: " + e);
		}
	}

	/**
	 * removes an event from the month view
	 *
	 * this removes any drag listeners from span elements
	 * for that event, and disables the drag
	 */
	this.removeTask = function(ttask){
		try{
			var settings = control.getSettingsManager();

			var arr = getTaskDOM(ttask);
			// var arr = this.eventDOMHolders.get(event.getId());
			if($obj(arr)){
				/**
				 * if we don't remove the drag listener
				 * then the event will always think it
				 * started dragging on the wrong day
				 * (the same day really, the day the event
				 *  was on when the page loaded)
				 */

				if($def(control.getDragManager)){
					// i have completed the drag
					// so remove the listener from
					// all of this events listings in month view
					//
					// i'll be added again by the day cell...
					var dm = control.getDragManager();
					dm.removeDragListener(arr, arr.monthViewDList);
					dm.disableDrag(arr);
				}

                var d = getDayCell(ttask.getDueDate());
                d.removeTaskDOM(arr);
                // update our parent
                arr.myParent = null;

//                if($obj(jive.ext.x.xParent(arr)) && jive.ext.x.xParent(arr) != null){
//					jive.ext.x.xParent(arr).removeChild(arr);
//					arr.myParent = null;
//				}else if($obj(arr.myParent) && arr.myParent != null){
//					arr.myParent.removeChild(arr);
//					arr.myParent = null;
//				}
				arr.killYourself();
			}
		}catch(e){
			alert("error removing task: " + ttask.getSubject() + "\nexception: " + e);
		}
	}


	/**
	 * this flushes a calendar entirely. it removes it from teh views,
	 * and also removes any DOM's we have cached for this calendar's
	 * events
	 */
	this.flushCalendar = function(cal){
		var events = that.eventDOMHolders.toArray(jive.gui.isMonthEventDOM);
		for(var i=0;i<events.length;i++){
			if(events[i].getEvent().getCalendarId() == cal.getId()){
				that.flushEvent(events[i].getEvent());
			}
		}
		var tasks = that.taskDOMHolders.toArray(jive.gui.isMonthTaskDOM);
		for(var i=0;i<tasks.length;i++){
			if(tasks[i].getTask().getCalendarId() == cal.getId()){
				that.flushTask(tasks[i].getTask());
			}
		}
	}

	/**
	 * this flushes an event entirely. it removes it from the views,
	 * and also removes any DOM's we have cached for that event
	 */
	this.flushEvent = function(tevent){
		try{
			that.removeEvent(tevent);
			that.eventDOMHolders.clear(tevent.getId());
			// also clear it from teh cache by calendar
			var cal_id = event2cal.get(tevent.getId());
			// clear the task from the old calendar cache
			var calHash = eventDOMbyCalHolder.get(cal_id);
			// we don't check for calHash existing here, b/c it must
			// b/c we added it to the cache in the removeEvent() func
			calHash.clear(tevent.getId());
		}catch(e){
			alert("error flushing event");
		}
	}

	/**
	 * this flushes a task entirely. it removes it from the views,
	 * and also removes any DOM's we have cached for that task
	 */
	this.flushTask = function(ttask){
		try{
			that.removeTask(ttask);
			that.taskDOMHolders.clear(ttask.getID());
			// also clear it from teh cache by calendar
			var cal_id = task2cal.get(ttask.getID());
			// clear the task from the old calendar cache
			var calHash = taskDOMbyCalHolder.get(cal_id);
			// we don't check for calHash existing here, b/c it must
			// b/c we added it to the cache in the getTaskDOM() func
			calHash.clear(ttask.getID());
		}catch(e){
			alert("flushEvent: " + e);
		}
	}


	/**
	 * refresh the text on the bar, possibly because
	 * of a settings change, etc
	 */
	this.refresh = function(){
		var settings = control.getSettingsManager();
		var arr = that.eventDOMHolders.toArray(jive.gui.isMonthEventDOM);
		try{
			for(var ri=0;ri<arr.length;ri++){
				//
				// compare the event against the two timezones,
				// and if teh start/end dates are different,
				// the referesh it
				//
				var tevent = arr[ri].getEvent()
				var old = settings.getOldTimezone();
				if(!jive.model.dateEQ(settings.adjustDate(tevent.getStart()), settings.adjustDate(tevent.getStart(), old)) ||
				   !jive.model.dateEQ(settings.adjustDate(tevent.getEnd()), settings.adjustDate(tevent.getEnd(), old))){
					that.flushEvent(tevent);
					that.addEvent(tevent);
				}
			}
		}catch(e){
			alert(e);
		}

		if(that.isExpandedHuh()){
			that.showMonth(aurora_gui.getCurrentDate());
		}
		that.refreshShading();
	}

	/**
	 * we've just now been placed in our parent div, so
	 * adjust height of our holders etc to fit
	 */
	this.init = function(veryinner){
		veryinner.appendChild(main_panel);
	}

	this.killYourself = function(){
		control = null;
		aurora_gui = null;
	}

	this.refreshWeather = function(){
		// loop through all dates in month view
		// and set weather
		var min = new Date();
		min.setTime(aurora_gui.getMinDate().getTime());

		while(jive.model.dateLTEQ(min, aurora_gui.getMaxDate())){

			var cell = getDayCell(min);
			var image = control.getSettingsManager().getWeatherImage(min);
			var color = cell.style.backgroundColor;
			if(image.length > 0){
				var left = 22;
				if(min.getDate() == 1){
					left = 42;
				}else{
					left = 22;
				}
//				cell.setAttribute("style", "background: url(" + image + ") " + left + "px 2px no-repeat " + cell.style.backgroundColor);
				cell.style.background =  "url(" + image + ") " + left + "px 2px no-repeat " + cell.style.backgroundColor;
			}else{
//				cell.setAttribute("style", "");
				cell.style.background =  "";
			}
			cell.style.backgroundColor = color;

			min.setDate(min.getDate() + 1);
		}

	}



	/**
	 * shows the month view for
	 * Date d
	 * @param d the date of the month to show
	 */
	this.refreshShading = function(){
//		if(loading_state == 1){
			// we've just loaded month view for the first time.
			// so no events/tasks are even in here yet, so don't
			// bother updating shading, when there aren't any
			// events/tasks anyways.
			//
//			return;
//		}
		var settings = control.getSettingsManager();
		var start_on = settings.getStartWeekOn();

		var dtemp = new Date();
		dtemp.setTime(aurora_gui.getMaxDate().getTime());

		var dt = new Date();
		dt.setTime(aurora_gui.getMinDate().getTime());

		// daylight savings time has a problem here...
		// so set the hour to 17, that way daylight savings
		// won't change the date by 2 days if we move 24 hours forward/back
		// temp_panel = this.getDayCellHolder(d);
		dt.setHours(17);
		// now set the date to the sunday (before|that) this month starts
		dt.setDate(1);

		var sub = dt.getDay();
		if(start_on != 0 && sub == 0){
			sub = 7;
		}
		dt.setDate(dt.getDate() - sub + start_on);

		// this will help us as we iterate through the days
		// it will keep track of what month the sunday for that
		// month is
		// this way, we can bail on the while loop once we've
		// finished showing this month
		var currMonth = new Date();
		currMonth.setTime(dt.getTime());

		var backgrounds = new Array();
		backgrounds[0] = "#FFFFFF";
		backgrounds[1] = "#EFEFEF";
		backgrounds[2] = "#DFDFDF";
		backgrounds[3] = "#CFCFCF";
		backgrounds[4] = "#BFBFBF";

		var now = settings.getNOW();

		var smart_shading = settings.getSmartShading();
		var current_month = aurora_gui.getCurrentDate().getMonth();
		// add all the day cells
		while(currMonth.getMonth() <= dtemp.getMonth() && currMonth.getYear() == dtemp.getYear() || currMonth.getYear() < dtemp.getYear()){
			var cell = getDayCell(dt);
			var dt_is_today = jive.model.dateEQ(dt, now);
			if(smart_shading){
				var num_events = cell.childNodes.length - 1;
				var true_num = 0;
				for(var i=0;i<num_events && true_num <= 4;i++){
					if(jive.ext.x.xDisplay(cell.childNodes[i+1]) == "block"){
						true_num++;
					}
				}

				if(true_num > 4){
					true_num = 4;
				}else if(true_num < 0){
					true_num = 0;
				}
				if(dt_is_today){
					cell.style.backgroundColor = "#e4f6e7";
					cell.outColor = "#e4f6e7";
				}else{
					cell.style.backgroundColor = backgrounds[num_events];
					cell.outColor = backgrounds[num_events];
				}
			}else if(cell.getDate().getMonth() != current_month){
				cell.style.backgroundColor = backgrounds[2];
				cell.outColor = backgrounds[2];
			}else{
				cell.style.backgroundColor = backgrounds[0];
				cell.outColor = backgrounds[0];
			}
			if(dt_is_today){
				cell.style.backgroundColor = "#e4f6e7";
				cell.setAttribute("class", "month_cell month_today_cell");
				cell.className = "month_cell month_today_cell";
				cell.overColor = "#ffffda";
			}else{
				cell.setAttribute("class", "month_cell month_day_cell");
				cell.className = "month_cell month_day_cell";
//				cell.overColor = "#f0f6fc";
				cell.overColor = "#ffffda";
			}

			dt.setDate(dt.getDate() + 1);
			if(dt.getDay() == 0){
				currMonth.setTime(dt.getTime());
			}
		}
		that.refreshWeather();
	}



	/**
	 * toggle event visibility if
	 * the calendar visibility is switched (ie, in the sidebar)
	 */
	this.calendarVisible = function(calendar, visibleHuh){
		var show_tasks_huh = control.getSettingsManager().getShowTasks();
		var objs = taskDOMbyCalHolder.get(calendar.getId());
		if($obj(objs) && objs != null){
			objs = objs.toArray();
			for(var i=0;i<objs.length;i++){
				if(visibleHuh && show_tasks_huh){
					jive.ext.x.xDisplayBlock(objs[i]);
				}else{
					jive.ext.x.xDisplayNone(objs[i]);
				}
			}
		}
		var objs = eventDOMbyCalHolder.get(calendar.getId());
		if($obj(objs) && objs != null){
			objs = objs.toArray();
			for(var i=0;i<objs.length;i++){
				for(var j=0; j<objs[i].length; j++){
					if(visibleHuh){
						jive.ext.x.xDisplayBlock(objs[i][j]);
					}else{
						jive.ext.x.xDisplayNone(objs[i][j]);
					}
				}
			}
		}
		that.refreshShading();
	}

	/**
	 * notify everybody else that a drag/drop happened
	 */
	this.stopDrag = function(tevent, dt, left, top){
		var doneHuh = false;
		if(that.isExpandedHuh()){
			/**
			 * loop through table cells
			 * if a point matches, then drop it in it's day cell
			 */
			if(main_panel.childNodes.length > 0){
				var table = main_panel.childNodes[0];
				for(var i=1;i<table.childNodes.length;i++){
					tr = table.childNodes[i];
					for(var j=0;j<tr.childNodes.length;j++){
						if(tr.childNodes[j].childNodes.length > 0){
							var day = tr.childNodes[j];
							if(jive.ext.x.xHasPoint(day, left, top)){
								if($def(day.dropPoint)){
									day.dropPoint(tevent,dt);
									doneHuh = true;
								}
							}
						}
					}
				}
			}
		}
		return doneHuh;
	}

	// this is the last day cell that
	// we've hovered over when dragging
	// an event or task
	var hovered_day_cell = null;
	var threadNum = 0;
	this.dragging = function(tevent, dt, left, tp){
		// this function will be called as they drag around an event
		// which means it'll be called probably as its running
		//
		// the threadNum and myThread variables will make sure that
		// I drop out of execution asap as a new thread starts.
		threadNum++;
		var myThread = threadNum;

		// track if we show a drop zone or not
		var zonedHuh = false;

		// let's check our currently hovered day cell
		// to see if that's what we're still hovered over.
		// if we're not over the same cell anymore, lets
		// just check each cell sequentially.
		//
		// later, we can optimize this to check nearby cells
		// first, instead of just checking /left->bottom/right
		//
		if(hovered_day_cell != null){
			if(jive.ext.x.xHasPoint(hovered_day_cell, left, tp)){
				var w = Math.floor(.95 * jive.ext.x.xWidth(hovered_day_cell));
				control.showHoverOver(jive.ext.x.xPageX(hovered_day_cell), jive.ext.x.xPageY(hovered_day_cell), w, jive.ext.x.xHeight(hovered_day_cell));
				zonedHuh = true;
			}
		}
		// loop through all dates in month view
		// and set weather
		if(main_panel.childNodes.length > 0 && !zonedHuh){
			var table = main_panel.childNodes[0];
			for(var i=1;i<table.childNodes.length && myThread == threadNum && !zonedHuh;i++){
				tr = table.childNodes[i];
				for(var j=0;j<tr.childNodes.length && myThread == threadNum && !zonedHuh;j++){
					var day = tr.childNodes[j];
					if(jive.ext.x.xHasPoint(day, left, tp)){
						if($def(day.dropPoint)){
							// hooray!
							// move the drag area here
							// and save this cell as
							// the last hovered
							var w = Math.floor(.95 * jive.ext.x.xWidth(day));
							control.showHoverOver(jive.ext.x.xPageX(day), jive.ext.x.xPageY(day), w, jive.ext.x.xHeight(day));
							hovered_day_cell = day;
							zonedHuh = true;
						}
					}
				}
			}
			if(myThread != threadNum){
				return;
			}
		}
		if(!zonedHuh){
			control.hideHover();
		}
		return zonedHuh;
	}


	/**********************************************************************
	***********************************************************************
	***********************************************************************
	**
	** END VIEW INTERFACE TO MONTH_VIEW
	**
	***********************************************************************
	***********************************************************************
	***********************************************************************/

	this.fixHeight = function(for_rows){
		try{
//			alert("updating height: " + for_rows);
			jive.ext.x.xHeight(main_panel, for_rows);
			var for_rows = for_rows - 20; // subtract 20 b/c of the header row
			if(main_panel.childNodes.length > 0){
				var table = main_panel.childNodes[0];
				for_rows += for_rows % (table.childNodes.length - 1);
				for(var i=1;i<table.childNodes.length;i++){
					var tr = table.childNodes[i];
					jive.ext.x.xHeight(tr, Math.floor(for_rows / (table.childNodes.length - 1)));
					if(jive.ext.x.xIE4Up){
//						alert("updating foo!");
						for(var j=0;j<tr.childNodes.length;j++){
							jive.ext.x.xDisplayNone(tr.childNodes[j]);
							jive.ext.x.xDisplayBlock(tr.childNodes[j]);
						}
					}
				}
			}
		}catch(e){
			alert(e);
		}
	}

	/**
	 * gets the events for a specific day
	 * @param dt a date object
	 */
	this.getEventsOn = function(dt){
		// we're going to load the day cell for this event
		// and get the events out of that
		var events = new Array();
		var cell = getDayCell(dt);
		for(var i=1; i < cell.childNodes.length; i++){
			if($def(cell.childNodes[i].getEvent)){
				events.push(cell.childNodes[i].getEvent());
			}
		}
		return events;
	}

	/**
	 * gets the tasks for a specific day
	 * @param dt a Date object
	 */
	this.getTasksOn = function(dt){
		// we're going to load the day cell for this event
		// and get the events out of that
		var tasks = new Array();
		var cell = getDayCell(dt);
        return cell.getTasks();
	}


	/**
	 * gets an array of dom objects for the event
	 * there will be enough dom objects to have 1 per day
	 * that the event spans
	 */
	function getDOMArray(tevent, skip){

		try{
			var settings = control.getSettingsManager();

			//
			// we store 1 DOM entry for each day the event is on
			//
			// however, if the dom entry would extend past teh min or max date
			// in month view, then we only cache the necessary dom entries.
			//
			// so if we're already caching the dom array, then lets make sure
			// that we're caching all of it. it could be that we're caching this months,
			// but we just loaded a whole new month and extended max date, so we need
			// to load in more dom's onto the array.
			//
			// if nothing is in the cache yet, then lets load in enough dom's
			// to either take care of the entire event's duration, or extend
			// until min or max date, whichever is shorter
			//
			//

			var iter = new Date();
			var end_iter = new Date();
			if(tevent.isAllDay()){
				iter.setTime(tevent.getStart().getTime());
				end_iter.setTime(tevent.getEnd().getTime());
			}else{
				iter.setTime(settings.adjustDate(tevent.getStart()).getTime());
				end_iter.setTime(settings.adjustDate(tevent.getEnd()).getTime());
			}

			//
			// iter is the start of the event
			// end_iter is the end of the event
			//
			// now lets make sure that iter >= minDate and end_iter <= maxDate
			//

			if(jive.model.dateLT(iter, control.getEventCache().getMinTime())){
				// iter is smaller, so change iter to min date
				iter.setTime(control.getEventCache().getMinTime().getTime());
			}
			if(jive.model.dateGT(end_iter, control.getEventCache().getMaxTime())){
				// end_iter is too large, so change end_iter to max date
				end_iter.setTime(control.getEventCache().getMaxTime().getTime());
			}
		}catch(e){
			alert("top of getdomarray: " + e);
		}

		try{

			var e_obj = that.eventDOMHolders.get(tevent.getId());
			if(!$obj(e_obj)){
				if($def(skip) && skip){
					return null;
				}
				e_obj = new Array();
				e_obj.getEvent = function(){ return tevent; }
				that.eventDOMHolders.put(tevent.getId(), e_obj);

				event2cal.put(tevent.getId(), tevent.getCalendarId());
				var calHash = eventDOMbyCalHolder.get(tevent.getCalendarId());
				if(!$obj(calHash) || calHash == null){
					calHash = new jive.ext.y.HashTable();
					eventDOMbyCalHolder.put(tevent.getCalendarId(), calHash);
				}
				calHash.put(tevent.getId(), e_obj);
			}

			var i=0;
			while(jive.model.dateLTEQ(iter, end_iter)){
				if(e_obj.length <= i){

					var formatDate = function(d){
						return function(d2){
							var dh = new jive.model.DateHelper(control);
							if(jive.model.dateEQ(settings.adjustDate(d2), d)){
								return dh.formatTo12HourTime(settings.adjustDate(d2));
							}else{
								return dh.formatToShortDate(settings.adjustDate(d2));
							}
						}
					}(iter);

					var txt = control.getEventDOMFactory().getEventDOM(tevent, aurora_gui.notifyEventClicked, aurora_gui.notifyEventDblClicked, formatDate);
					txt.showTimes(false);
					// make a field to store our parent DOM node
					// it's false b/c we don't have a parent yet.
					txt.getDOM().myParent = null;
					txt.getDOM().killYourself = txt.killYourself;
					txt.getDOM().refresh = txt.refresh;
					txt.getDOM().lighten = txt.lighten;
					txt.getDOM().darken = txt.darken;

					txt = txt.getDOM();

					e_obj[i] = txt;
				}else{
					e_obj[i].refresh();
				}

				i++;
				iter.setDate(iter.getDate() + 1);
			}
			while(e_obj.length > i){
				if($obj(jive.ext.x.xParent(e_obj[i])) && jive.ext.x.xParent(e_obj[i]) != null){
					jive.ext.x.xParent(e_obj[i]).removeChild(e_obj[i]);
					e_obj[i].myParent = null;
				}else if($obj(e_obj[i].myParent) && e_obj[i].myParent != null){
					e_obj[i].myParent.removeChild(e_obj[i]);
					e_obj[i].myParent = null;
				}
				e_obj[i].killYourself();
				e_obj.splice(i,1);
			}

			for(var i=0;i<e_obj.length;i++){
				if(that.getItemVisibility()){
					e_obj[i].style.visibility = "visible";
				}else{
					e_obj[i].style.visibility = "hidden";
				}
			}

			return e_obj;
		}catch(e){
			alert("getting array dom in month: " + e);
		}
	}

	function getTaskDOM(ttask){
		var settings = control.getSettingsManager();

		var holder = that.taskDOMHolders.get(ttask.getID());

		if($obj(holder) && holder != null){
			holder.refresh();
			return holder;
		}



		var taskdom = new jive.gui.TaskDOM(control, ttask, aurora_gui.notifyTaskClicked, aurora_gui.notifyTaskDblClicked);
		var holder = taskdom.getDOM();

		holder.refresh = taskdom.refresh;
		holder.lighten = taskdom.lighten;
		holder.darken = taskdom.darken;
		holder.getTask = taskdom.getTask;

		// we have to forward the killYourself function
		// when this task is unloaded / removed
		holder.killYourself = taskdom.killYourself;

		// make a field to store our parent DOM node
		// it's false b/c we don't have a parent yet.
		holder.myParent = null;

		that.taskDOMHolders.put(ttask.getID(), holder);

		task2cal.put(ttask.getID(), ttask.getProjectID());
		var calHash = taskDOMbyCalHolder.get(ttask.getProjectID());
		if(!$obj(calHash) || calHash == null){
			calHash = new jive.ext.y.HashTable();
			taskDOMbyCalHolder.put(ttask.getProjectID(), calHash);
		}
		calHash.put(ttask.getID(), holder);

		if(that.getItemVisibility()){
			holder.style.visibility = "visible";
		}else{
			holder.style.visibility = "hidden";
		}


		return holder;
	}

	/**
	 * private
	 * retrieves/creates the day cell for the
	 * specified time
	 */
	function getDayCell(dt){
		var dtemp = new Date();
		dtemp.setTime(dt.getTime());

		var hash = dtemp.getDate();
		var dobj = that.dayCells.get(hash);
		if($arr(dobj)){
			for(var i=0;i<dobj.length;i++){
				//
				// we already know the date is the same
				// b/c that's teh hash
				if(jive.model.monthYearEQ(dobj[i].getDate(), dtemp)){
					return dobj[i].getDOM();
				}
			}
		}else{
			dobj = new Array();
			that.dayCells.put(hash, dobj);
		}

		var day = new jive.gui.MonthDayCell(control, aurora_gui, that, dtemp);
		dobj.push(day);
		return day.getDOM();
	}


	/**
	 * shows the month view for
	 * Date d
	 * @param d the date of the month to show
	 */
	var last_month_min = null;
	var last_month_max = null;

	var loading_state = 0;
	this.showMonth = function(dtemp){
		var settings = control.getSettingsManager();
		var start_on = settings.getStartWeekOn();

		loading_state++;

		expanded = true;

		var d = new Date();
		d.setTime(dtemp.getTime());
		// daylight savings time has a problem here...
		// so set the hour to 17, that way daylight savings
		// won't change the date by 2 days if we move 24 hours forward/back
		// temp_panel = this.getDayCellHolder(d);
		d.setHours(17);

		// now set the date to the sunday (before|that) this month starts
		d.setDate(1);
		var sub = d.getDay();
		if(start_on != 0 && sub == 0){
			sub = 7;
		}
		d.setDate(d.getDate() - sub + start_on);

		// this will help us as we iterate through the days
		// it will keep track of what month the sunday for that
		// month is
		// this way, we can bail on the while loop once we've
		// finished showing this month
		var currMonth = new Date();
		currMonth.setTime(dtemp.getTime());

		var lang = control.getLanguageManager().getActiveLanguage();
		var affectedTasks = new Array();
		if(force_month_view || last_month_min == null || !jive.model.dateEQ(last_month_min, d)){
			force_month_view = false;
			/**
			 * set up the minimum date
			 */
			var currMin = new Date();
			currMin.setTime(d.getTime());
			aurora_gui.setMinDate(currMin);

			// add all the day cells
			var cell;
			var td;
			var tr;
			var table = document.createElement('DIV');
			table.setAttribute("class", "month_table");
			table.className = "month_table";
			while(main_panel.childNodes.length > 0) main_panel.removeChild(main_panel.childNodes[0]);

			tr = document.createElement('DIV');
			table.appendChild(tr);
			jive.ext.x.xHeight(tr, 20);

			for(var i=start_on;i-start_on<7;i++){
				td = document.createElement('DIV');
				tr.appendChild(td);
				td.setAttribute("class", "month_table_th");
				td.className = "month_table_th";
				if(jive.ext.x.xWidth(main_panel) > 640){
					td.appendChild(document.createTextNode(lang.longDay(i%7)));
				}else{
					td.appendChild(document.createTextNode(lang.shortDay(i%7)));
				}
				td.style.left = ((i-start_on) * 14.2857) + "%";
			}

			var rows = new Array();
			var weekday_num = 0;
			while(currMonth.getMonth() <= dtemp.getMonth() && currMonth.getYear() == dtemp.getYear() || currMonth.getYear() < dtemp.getYear()){
				if(d.getDay() == start_on){
					tr = document.createElement('DIV');
					tr.setAttribute("class","month_table_row");
					tr.className = "month_table_row";
					rows.push(tr);
					weekday_num = 0;
				}
				td = getDayCell(d);
				td.updateText();
				td.over = false;
				tr.appendChild(td);
				td.style.left = (weekday_num * 14.2857) + "%";
				weekday_num++;


				var foo = new Date();
				foo.setTime(d.getTime());
				d = foo;
				d.setDate(d.getDate() + 1);
				if(d.getDay() == start_on){
					currMonth.setTime(d.getTime());
				}
				if(jive.ext.x.xIE4Up){
					// IE has a bug where it doesn't preserve checkbox's on/off state
					// so we need to fix it here...
					var tasks = that.getTasksOn(d);
					affectedTasks = affectedTasks.concat(tasks);
				}
			}
			for(var i=0;i<rows.length;i++){
				table.appendChild(rows[i]);
			}
			/**
			 * set up the maximum date
			 */
			aurora_gui.setMaxDate(d);

			var currDate = new Date();
			currDate.setTime(dtemp.getTime());
			currDate.setHours(17);
			aurora_gui.setCurrentDate(currDate);

			var lang = control.getLanguageManager().getActiveLanguage();

//			if(!jive.model.dateLTEQ(last_month_min, aurora_gui.getMinDate()) || !jive.model.dateGTEQ(last_month_max, aurora_gui.getMaxDate())){
//				// refresh the shading
//				that.notifyTimesChanged(aurora_gui.getMinDate(), aurora_gui.getMaxDate());
//			}

			main_panel.appendChild(table);

			for(var i=1;i<table.childNodes.length;i++){
				var tr = table.childNodes[i];
//				if($def(tr.style.height)) tr.style.height = (100/(table.childNodes.length-1) - .1) + "%";
				for(var j=0;j<tr.childNodes.length;j++){
					var cell = tr.childNodes[j];
					if(cell.childNodes.length > 0){
						jive.ext.x.xZIndex(cell.childNodes[0], 10 + i);
					}
				}
			}

			last_month_min = new Date();
			last_month_max = new Date();
			last_month_min.setTime(aurora_gui.getMinDate().getTime());
			last_month_max.setTime(aurora_gui.getMaxDate().getTime());
		}else{
			if(jive.ext.x.xIE4Up){
				while(currMonth.getMonth() <= dtemp.getMonth() && currMonth.getYear() == dtemp.getYear() || currMonth.getYear() < dtemp.getYear()){
					var foo = new Date();
					foo.setTime(d.getTime());
					d = foo;
					d.setDate(d.getDate() + 1);
					if(d.getDay() == start_on){
						currMonth.setTime(d.getTime());
					}
					// IE has a bug where it doesn't preserve checkbox's on/off state
					// so we need to fix it here...
					var tasks = that.getTasksOn(d);
					affectedTasks = affectedTasks.concat(tasks);
				}
			}
			aurora_gui.setMinDate(last_month_min);
			aurora_gui.setMaxDate(last_month_max);
		}

		// IE has a bug where it doesn't preserve checkbox's on/off state
		// so we need to fix it here...
		if(jive.ext.x.xIE4Up){
			for(var i=0;i<affectedTasks.length;i++){
				var dom = getTaskDOM(affectedTasks[i]);
				dom.refresh();
			}
		}
		that.refreshShading();
		var t = aurora_gui.getFilterText();
		that.filter(t);
		aurora_gui.fixHeight();
	}


	/**
	 * unselects teh current event in the view
	 */
	function unselectAll(){
		if(jive.model.isEvent(aurora_gui.getSelectedItem())){
			var e_obj = getDOMArray(aurora_gui.getSelectedItem());
			for(var i=0;i<e_obj.length;i++){
				filterNode(e_obj[i], aurora_gui.getFilterText());
			}
		}
		if(jive.model.isTask(aurora_gui.getSelectedItem())){
			var e_obj = getTaskDOM(aurora_gui.getSelectedItem());
			filterNode(e_obj, aurora_gui.getFilterText());
		}
	}


	function ensureStartDates(){
		var start_on = control.getSettingsManager().getStartWeekOn();
		var dtemp = aurora_gui.getCurrentDate();
		var d = new Date();
		d.setTime(dtemp.getTime());
		// daylight savings time has a problem here...
		// so set the hour to 17, that way daylight savings
		// won't change the date by 2 days if we move 24 hours forward/back
		// temp_panel = this.getDayCellHolder(d);
		d.setHours(17);

		// now set the date to the sunday (before|that) this month starts
		d.setDate(1);
		var sub = d.getDay();
		if(start_on != 0 && sub == 0){
			sub = 7;
		}
		d.setDate(d.getDate() - sub + start_on);

		// this will help us as we iterate through the days
		// it will keep track of what month the sunday for that
		// month is
		// this way, we can bail on the while loop once we've
		// finished showing this month
		var currMonth = new Date();
		currMonth.setTime(dtemp.getTime());


		aurora_gui.setMinDate(d);
		while(currMonth.getMonth() <= dtemp.getMonth() && currMonth.getYear() == dtemp.getYear() || currMonth.getYear() < dtemp.getYear()){
			var foo = new Date();
			foo.setTime(d.getTime());
			d = foo;
			d.setDate(d.getDate() + 1);
			if(d.getDay() == start_on){
				currMonth.setTime(d.getTime());
			}
		}
		var currMax = new Date();
		currMax.setTime(d.getTime());
		currMax.setDate(currMax.getDate() + 1);
		aurora_gui.setMaxDate(currMax);

		var currDate = new Date();
		currDate.setTime(dtemp.getTime());
		currDate.setHours(17);
		aurora_gui.setCurrentDate(currDate);

		last_month_min = new Date();
		last_month_max = new Date();
		last_month_min.setTime(aurora_gui.getMinDate().getTime());
		last_month_max.setTime(aurora_gui.getMaxDate().getTime());

		force_month_view = true;
	}

	function updateShowTasks(){
		// show/hide tasks based on preference
		var show_huh = control.getSettingsManager().getShowTasks();
		if(show_huh){
			// loop through calendars,
			// and if the calendar is visible, then
			// get all the task doms in that calendar
			// and displayBlock
			var cals = control.getCalendarCache().getCalendars();
			for(var i=0;i<cals.length;i++){
				if(control.isCalendarVisibleHuh(cals[i].getId())){
					var calDomHash = taskDOMbyCalHolder.get(cals[i].getId());
					if($obj(calDomHash)){
						// displayBlock everything
						var tasks = calDomHash.toArray();
						for(var j=0; j<tasks.length; j++){
							if($def(tasks[j].getTask)){
								jive.ext.x.xDisplayBlock(tasks[j]);
							}
						}
					}
				}
			}
		}else{
			// displayNone everything
			var tasks = that.taskDOMHolders.toArray(jive.gui.isMonthTaskDOM);
			for(var i=0; i<tasks.length; i++){
				if($def(tasks[i].getTask)){
					jive.ext.x.xDisplayNone(tasks[i]);
				}
			}
		}
	}

	/************************************************
	 * listen to stuff
	 ************************************************/

	/**
	 * add a listener to listen for event clicks
	 * when an event is clicked, highlight it in
	 * the main view, and unhighlight (if needed)
	 * the previously highlighted event
	 */
	var list = new Object();
	list.eventClicked = function(tevent){
		unselectAll();
		// select the event
		var e_obj = getDOMArray(tevent);
		if($obj(e_obj)){
			for(var i=0;i<e_obj.length;i++){
				e_obj[i].setAttribute("class","month_day_cell_item_highlight");
				e_obj[i].className = "month_day_cell_item_highlight";
			}
		}
	}
	list.eventDblClicked = function(tevent){ }
	list.taskClicked = function(ttask){
		unselectAll();
		// select the task
		var e_obj = getTaskDOM(ttask);
		if($obj(e_obj)){
			e_obj.setAttribute("class","month_day_cell_item_highlight");
			e_obj.className = "month_day_cell_item_highlight";
		}
	}
	list.taskDblClicked = function(ttask){ }
	list.unselectAll = function(){
		unselectAll();
	}
	aurora_gui.addEventListener(list);

	/**
	 * listen to event cache
	 * when we load an event, tell the month view
	 */

    /**
     * This code is used to manage events
     * for this UI
    var list = new jive.model.EventCacheListener();
	list.loadTask = function(ttask){
		if(ttask.hasDueDate()){
			that.addTask(ttask);
		}
	}
	list.doneLoadingEvents = function(){
		// refresh the shading
		that.refreshShading();
	}
	list.doneSavingEvent = function(tevent){
		loading_state++;
		// refresh the shading
		that.refreshShading();
		// also, udpate it's cache by calendar id
		var old_cal = event2cal.get(tevent.getId());
		if(old_cal != tevent.getCalendarId()){
			var obj = getDOMArray(tevent);

			// clear the event from the old calendar cache
			var calHash = eventDOMbyCalHolder.get(old_cal);
			calHash.clear(tevent.getId());
			// add it to the correct calendar cache
			var calHash = eventDOMbyCalHolder.get(tevent.getCalendarId());
			if(!$obj(calHash) || calHash == null){
				calHash = new jive.ext.y.HashTable();
				eventDOMbyCalHolder.put(tevent.getCalendarId(), calHash);
			}
			calHash.put(tevent.getId(), obj);
		}
	}
	list.savingEventFailed = function(){
		// refresh the shading
		that.refreshShading();
	}
	list.savingTask = function(ttask){
		var obj = getTaskDOM(ttask);
		obj.setDisabled(true);
	}
	list.doneSavingTask = function(ttask){
		// refresh the shading
		loading_state++;
		that.refreshShading();
		if(ttask.hasDueDate()){
			var obj = getTaskDOM(ttask);
			obj.refresh();
			obj.setDisabled(false);
		}
		// also, udpate it's cache by calendar id
		var old_cal = task2cal.get(ttask.getID());
		if(old_cal != ttask.getProjectID()){
			// clear the task from the old calendar cache
			var calHash = taskDOMbyCalHolder.get(old_cal);
			calHash.clear(ttask.getID());
			// add it to the correct calendar cache
			var calHash = taskDOMbyCalHolder.get(ttask.getProjectID());
			if(!$obj(calHash) || calHash == null){
				calHash = new jive.ext.y.HashTable();
				taskDOMbyCalHolder.put(ttask.getProjectID(), calHash);
			}
			calHash.put(ttask.getID(), obj);
		}
	}
	list.savingTaskFailed = function(ttask){
		// refresh the shading
		that.refreshShading();
		var obj = getTaskDOM(ttask);
		obj.setDisabled(false);
		obj.setChecked(ttask.getStatus() == "Complete");
	}
	list.doneDeletingEvent = function(tevent){
		// refresh the shading
		that.refreshShading();
	}
	list.deletingEventFailed = function(tevent){
		// refresh the shading
		that.refreshShading();
	}
	list.deletingEventSeries = function(tevent){
		// refresh the shading
		that.refreshShading();
	}
	list.doneDeletingEventSeries = function(tevent){
		// refresh the shading
		that.refreshShading();
	}
	list.deletingEventSeriesFailed = function(tevent){
		// refresh the shading
		that.refreshShading();
	}
	list.eventChanged = function(tevent){
		// refresh the shading
		that.refreshShading();
	}
	list.deletingTask = function(ttask){
		// refresh the shading
		that.refreshShading();
	}
	list.doneDeletingTask = function(ttask){
		// refresh the shading
		that.refreshShading();
	}
	list.deletingTaskFailed = function(ttask){
		// refresh the shading
		that.refreshShading();
	}
	list.deletingTaskSeries = function(ttask){
		// refresh the shading
		that.refreshShading();
	}
	list.doneDeletingTaskSeries = function(ttask){
		// refresh the shading
		that.refreshShading();
	}
	list.deletingTaskSeriesFailed = function(ttask){
		// refresh the shading
		that.refreshShading();
	}
	list.taskChanged = function(ttask){
		// refresh the shading
		that.refreshShading();
	}
	list.doneAddingEvents = function(){
		// refresh the shading
		that.refreshShading();
	}
	control.getEventCache().addListener(list);
     *
     */
    var list = new jive.model.TaskCacheListener();
	list.loadTask = function(ttask){
		if(ttask.hasDueDate()){
			that.addTask(ttask);
		}
	}
	list.doneLoadingTasks = function(){
		// refresh the shading
		that.refreshShading();
	}
    list.taskChanged = function(ttask){
        // refresh the shading
        that.refreshShading();

    }

	list.savingTask = function(ttask){
		var obj = getTaskDOM(ttask);
		obj.setDisabled(true);
	}
	list.doneSavingTask = function(ttask){
		// refresh the shading
		loading_state++;
		that.refreshShading();
		if(ttask.hasDueDate()){
			var obj = getTaskDOM(ttask);
			obj.refresh();
			obj.setDisabled(false);
		}
		// also, udpate it's cache by calendar id
		var old_cal = task2cal.get(ttask.getID());
		if(old_cal != ttask.getProjectID()){
			// clear the task from the old calendar cache
			var calHash = taskDOMbyCalHolder.get(old_cal);
			calHash.clear(ttask.getID());
			// add it to the correct calendar cache
			var calHash = taskDOMbyCalHolder.get(ttask.getProjectID());
			if(!$obj(calHash) || calHash == null){
				calHash = new jive.ext.y.HashTable();
				taskDOMbyCalHolder.put(ttask.getProjectID(), calHash);
			}
			calHash.put(ttask.getID(), obj);
		}
	}
	list.savingTaskFailed = function(ttask){
		// refresh the shading
		that.refreshShading();
		var obj = getTaskDOM(ttask);
		obj.setDisabled(false);
		obj.setChecked(ttask.getStatus() == "Complete");
	}
	list.deletingTask = function(ttask){
		// refresh the shading
		that.refreshShading();
	}
	list.doneDeletingTask = function(ttask){
		// refresh the shading
		that.refreshShading();
	}
	list.deletingTaskFailed = function(ttask){
		// refresh the shading
		that.refreshShading();
	}
	list.deletingTaskSeries = function(ttask){
		// refresh the shading
		that.refreshShading();
	}
	list.doneDeletingTaskSeries = function(ttask){
		// refresh the shading
		that.refreshShading();
	}
	list.deletingTaskSeriesFailed = function(ttask){
		// refresh the shading
		that.refreshShading();
	}
	control.getTaskCache().addListener(list);
	/*************************************************
	 * last bit of initialization
	 *************************************************/
	ensureStartDates();
	jive.ext.x.xDisplayNone(main_panel);
}



jive.gui.isMonthEventDOM = function(item){
	return $def(item) && $def(item.getEvent);
}
jive.gui.isMonthTaskDOM = function(item){
	return $def(item) && $def(item.getTask);
}


/**
 * This is a month view that shows the next 2 weeks of dates only
 */
jive.gui.MiniMonthView = function(control, aurora_gui){


	var that = this;

	var expanded = false;


	var has_add_view = null;

	this.hasAddView = function(){
		if(has_add_view == null){
			has_add_view = $obj(aurora_gui.getView("add_event"));
		}
		return has_add_view;
	}

	/**
	 * holds day cells
	 */
	this.dayCells = new jive.ext.y.HashTable();

	/**
	 * holds the spans that hold the event titles
	 * will return an array for a key
	 * the array will hold all of the title spans for that event
	 *
	 * ie, if an event spans the days, it will have an array of 3 spans
	 * one for each day cell
	 */
	this.eventDOMHolders = new jive.ext.y.HashTable();
	this.taskDOMHolders = new jive.ext.y.HashTable();
    this.cpDOMHolders = new jive.ext.y.HashTable();

    /**
	 * set to true if showMonth() should ignore
	 * optimizing by not showing month if the date is the same
	 * as is currently shown.
	 *
	 * ie, set to true to force showMonth to do something
	 * meaningful
	 */
	var force_month_view = false;

	/**
	 * we're also going to cache
	 * the event and task by its calendar id
	 */
	var taskDOMbyCalHolder = new jive.ext.y.HashTable();
	var eventDOMbyCalHolder = new jive.ext.y.HashTable();
    var cpDOMbyCalHolder = new jive.ext.y.HashTable();

    /**
	 * track which calendar id's we're caching each task/event by
	 */
    var task2cal = new jive.ext.y.HashTable();
    var cp2cal = new jive.ext.y.HashTable();
	var event2cal = new jive.ext.y.HashTable();

	/**
	 * this is the main panel that holds all the day cell holders
	 */
	var main_panel = document.createElement('DIV');
	main_panel.setAttribute("class", "month_view_holder");
	main_panel.className = "month_view_holder";


	var visi = true;
	this.setItemVisibility = function(b){
		visi = b;
	}
	this.getItemVisibility = function(){
		return visi;
	}

    function getTaskDOM(ttask){
        var holder = that.taskDOMHolders.get(ttask.getID());

        if($obj(holder) && holder != null){
            holder.refresh();
            return holder;
        }

        var taskdom = new jive.gui.TaskDOM(control, ttask, aurora_gui.notifyTaskClicked, aurora_gui.notifyTaskDblClicked);
        holder = taskdom.getDOM();

        holder.refresh = taskdom.refresh;
        holder.lighten = taskdom.lighten;
        holder.darken = taskdom.darken;
        holder.getTask = taskdom.getTask;

        // we have to forward the killYourself function
        // when this task is unloaded / removed
        holder.killYourself = taskdom.killYourself;

        // make a field to store our parent DOM node
        // it's false b/c we don't have a parent yet.
        holder.myParent = null;

        that.taskDOMHolders.put(ttask.getID(), holder);

        task2cal.put(ttask.getID(), ttask.getProjectID());
        var calHash = taskDOMbyCalHolder.get(ttask.getProjectID());
        if(!$obj(calHash) || calHash == null){
            calHash = new jive.ext.y.HashTable();
            taskDOMbyCalHolder.put(ttask.getProjectID(), calHash);
        }
        calHash.put(ttask.getID(), holder);

        if(that.getItemVisibility()){
            holder.style.visibility = "visible";
        }else{
            holder.style.visibility = "hidden";
        }


        return holder;
    }

    function getCPDOM(cp){

        var holder = that.cpDOMHolders.get(cp.getID());

        if($obj(holder) && holder != null){
            holder.refresh();
            return holder;
        }

        var cpdom = new jive.gui.CPDOM(control, cp, aurora_gui.notifyCheckPointClicked, aurora_gui.notifyCheckPointDblClicked);
        holder = cpdom.getDOM();

        holder.refresh = cpdom.refresh;
        holder.lighten = cpdom.lighten;
        holder.darken = cpdom.darken;
        holder.getCheckPoint = cpdom.getCheckPoint;

        // we have to forward the killYourself function
        // when this task is unloaded / removed
        holder.killYourself = cpdom.killYourself;

        // make a field to store our parent DOM node
        // it's false b/c we don't have a parent yet.
        holder.myParent = null;

        that.cpDOMHolders.put(cp.getID(), holder);

        cp2cal.put(cp.getID(), cp.getProject().getID());
        var calHash = cpDOMbyCalHolder.get(cp.getProject().getID());
        if(!$obj(calHash) || calHash == null){
            calHash = new jive.ext.y.HashTable();
            cpDOMbyCalHolder.put(cp.getProject().getID(), calHash);
        }
        calHash.put(cp.getID(), holder);

        if(that.getItemVisibility()){
            holder.style.visibility = "visible";
        }else{
            holder.style.visibility = "hidden";
        }

        return holder;
    }

    /**
     * private
     * retrieves/creates the day cell for the
     * specified time
     */
    function getDayCell(dt){
        var dtemp = new Date();
        dtemp.setTime(dt.getTime());

        var hash = dtemp.getDate();
        var dobj = that.dayCells.get(hash);
        if($arr(dobj)){
            for(var i=0;i<dobj.length;i++){
                //
                // we already know the date is the same
                // b/c that's teh hash
                if(jive.model.monthYearEQ(dobj[i].getDate(), dtemp)){
                    return dobj[i].getDOM();
                }
            }
        }else{
            dobj = new Array();
            that.dayCells.put(hash, dobj);
        }

        var day = new jive.gui.MonthDayGroupedCell(control, aurora_gui, that, dtemp);
        dobj.push(day);
        return day.getDOM();
    }



	/**********************************************************************
	***********************************************************************
	***********************************************************************
	**
	** BEGIN VIEW INTERFACE TO AURORA GUI
	**
	***********************************************************************
	***********************************************************************
	***********************************************************************/

	/**
	 * return true if we can print this view, false otherwise
	 */
	this.hasPrintView = function(){
		return true;
	}

	/**
	 * this returns true if week view is the current view, false otherwise
	 */
	this.isExpandedHuh = function(){
		return expanded;
	}

	/**
	 * this function is called when week view comes into view
	 * we have officially 'switched' the view to week view
	 */
	this.expand = function(){
		expanded = true;
		aurora_gui.showArrows();
		jive.ext.x.xDisplayBlock(main_panel);
	}

	/**
	 * this function is called when week view goes out of view
	 * we have officially 'switched out' the view from week view
	 */
	this.collapse = function(){
		expanded = false;
		jive.ext.x.xDisplayNone(main_panel);
	}

	// the function to switch to the previous month
	// (used in the previous button)
	function prevMonthFunc(){
		var d = new Date();
		d.setTime(aurora_gui.getCurrentDate().getTime());
		jive.model.dateMinusMonth(d);
		aurora_gui.setCurrentDate(d);
		aurora_gui.notifyMonthClicked(d);
	}
	// the function to switch to the next month
	// (used in the next button)
	function nextMonthFunc(){
		var d = new Date();
		d.setTime(aurora_gui.getCurrentDate().getTime());
		d.setMonth(d.getMonth()+1);
		while(d.getMonth() > aurora_gui.getCurrentDate().getMonth()+1) jive.model.dateMinusDay(d);
		aurora_gui.setCurrentDate(d);
		aurora_gui.notifyMonthClicked(d);
	}

	/**
	 * return the function to go back a month
	 */
	this.getPrevViewFunc = function(){
		// the function to switch to the previous week
		// (used in the previous button)
		return prevMonthFunc;
	}

	/**
	 * return the function to go forward a month
	 */
	this.getNextViewFunc = function(){
		// the function to switch to the next week
		// (used in the next button)
		return nextMonthFunc;
	}

	/**
	 * when this view is in range, calcualte the min date that should be in range
	 * given month view's getCurrentDate() function
	 */
	this.getMinDate = function(){
		return aurora_gui.getMinDate();
	}

	/**
	 * when this view is in range, calcualte the min date that should be in range
	 * given month view's getCurrentDate() function
	 */
	this.getMaxDate = function(){
		return aurora_gui.getMaxDate();
	}

	/**
	 * returns teh text that should be in month view's header
	 * this is based on month_view's getCurrentDate() function
	 */
	this.getHeaderText = function(){
		var lang = control.getLanguageManager().getActiveLanguage();

		var d = new Date();
		d.setTime(aurora_gui.getCurrentDate().getTime());

		return lang.longMonth(d.getMonth());
	}

	/**
	 * shows the month according to month view's current date
	 */
	this.go = function(d){
		aurora_gui.setCurrentDate(d);
		that.showMonth(aurora_gui.getCurrentDate());
	}

	/**
	 * return the name of this view
	 */
	this.getName = function(){
		return "month";
	}

	/**
	 * return the unique hash for this view
	 */
	this.getHash = function(){
		return "month";
	}


	/**
	 * the language has been updated,
	 */
	this.updateText = function(){
		if(main_panel.childNodes.length > 0){
			var start_on = control.getSettingsManager().getStartWeekOn();
			var lang = control.getLanguageManager().getActiveLanguage();
			for(var i=start_on;i-start_on<7;i++){
				var td = main_panel.childNodes[0].childNodes[0].childNodes[(i-start_on)%7]; // the table cell
				if(td.childNodes.length > 0) td.removeChild(td.childNodes[0]);
				td.appendChild(document.createTextNode(lang.longDay(i%7)));
				td.setAttribute("height", "2");
				td.height = "2";
			}
		}
	}

	/**
	 * return the DOM object that represents this month view
	 */
	this.getDOM = function(){
		return main_panel;
	}


	/**
	 * this flushes a calendar entirely. it removes it from teh views,
	 * and also removes any DOM's we have cached for this calendar's
	 * events
	 */
	var filter_time;
	function filterNode(node, str){
		var title;
		var desc;
		var cal_id;
		if($def(node.getEvent)){
			var event = node.getEvent();
			title = event.getSubject().toLowerCase();
			desc = event.getDescription().toLowerCase();
			cal_id = event.getCalendarId();
		}else
		if($def(node.getTask)){
			var task = node.getTask();
			title = task.getSubject().toLowerCase();
			desc = task.getDescription().toLowerCase();
			cal_id = task.getProjectID();
		}
		if(control.isCalendarVisibleHuh(cal_id)){
			if(str.length == 0  || str.length > 0 && (title.indexOf(str) >= 0 || desc.indexOf(str) >= 0)){
//				alert("showing: " + cell.childNodes[i].innerText);
				node.darken();
			}else{
//				alert("hiding: " + cell.childNodes[i].innerText);
				node.lighten();
			}
		}
	}
	var last_filter = "";
	var last_filter_month = (new Date()).getMonth();
	this.filter = function(str){
		//
		// don't bother filtering if it's
		// the same thing we did last time
		if(last_filter != str || last_filter_month != aurora_gui.getCurrentDate().getMonth()){
			last_filter = str;
			last_filter_month = aurora_gui.getCurrentDate().getMonth()
			if(that.isExpandedHuh()){
				filter_time = "" + (new Date()).getTime() + "" + Math.random();
				var my_time = filter_time;
				var dt = new Date();
				var min = aurora_gui.getMinDate();
				var max = aurora_gui.getMaxDate();
				dt.setTime(min.getTime());
				str = str.toLowerCase();
				while(my_time == filter_time && (dt.getTime() < max.getTime() + 24*60*60*1000)){
					var cell = getDayCell(dt);
					for(var i=1; i < cell.childNodes.length; i++){
						filterNode(cell.childNodes[i], str);
					}
					dt.setTime(dt.getTime() + 24*60*60*1000);
				}
			}
		}
	}


	/**
	 * add (or refresh) an event to display on this month view
	 * i need to update this function. its not handling teh drag listeners
	 * correctly. i need to remove old listeners before i add new ones...
	 */
	this.addEvent = function(tevent){
		try{
			var settings = control.getSettingsManager();

			var e_obj = getDOMArray(tevent);



			var iter = new Date();
			if(tevent.isAllDay()){
				iter.setTime(tevent.getStart().getTime());
			}else{
				iter.setTime(settings.adjustDate(tevent.getStart()).getTime());
			}
			var i=0;
			var end_iter = new Date();
			if(tevent.isAllDay()){
				end_iter.setTime(tevent.getEnd().getTime());
			}else{
				end_iter.setTime(settings.adjustDate(tevent.getEnd()).getTime());
			}

			if(jive.model.dateLT(iter, control.getEventCache().getMinTime())){
				// iter is smaller, so change iter to min date
				iter.setTime(control.getEventCache().getMinTime().getTime());
			}
			if(jive.model.dateGT(end_iter, control.getEventCache().getMaxTime())){
				// end_iter is too large, so change end_iter to max date
				end_iter.setTime(control.getEventCache().getMaxTime().getTime());
			}


			while(jive.model.dateLTEQ(iter, end_iter)){
				var d = getDayCell(iter);
				// update the listener
				if($def(control.getDragManager)){
					var dm = control.getDragManager();
					var dragDate = new Date();
					dragDate.setTime(iter.getTime());
					var dlist = new jive.gui.CellDragListener(control, tevent, dragDate, control.notifyStopDrag, control.notifyDragging);
					if($obj(e_obj[i].monthViewDList) && e_obj[i].monthViewDList != null){
						dm.removeDragListener(e_obj[i], e_obj[i].monthViewDList);
					}
					e_obj[i].monthViewDList = dlist;
					dm.enableDrag(e_obj[i]);
					dm.addDragListener(e_obj[i], dlist);
				}
				/**
				 * hide the event if the calendar is hidden
				 */
				if(!control.isCalendarVisibleHuh(tevent.getCalendarId())){
					jive.ext.x.xDisplayNone(e_obj[i]);
				}else{
					jive.ext.x.xDisplayBlock(e_obj[i]);
				}
				var t = aurora_gui.getFilterText();
				filterNode(e_obj[i], t)
				d.appendEventDOM(e_obj[i]);
				// update our parent
				e_obj[i].myParent = d;

				i++;
				iter.setDate(iter.getDate() + 1);

			}
		}catch(e){
			alert("error adding event to month view: " + e);
		}
	}



    /**
     * add a task to display on this month view
     */
    this.addCheckPoint = function(cp){
        try{
            var e_obj = getCPDOM(cp);

            var d = getDayCell(cp.getDueDate());
            d.appendCheckPointDOM(e_obj);
            // update our parent
            e_obj.myParent = d;

            // update text/checkbox
            e_obj.refresh();

            // update the drag listener
            if($def(control.getDragManager)){
                var dm = control.getDragManager();
                var dragDate = new Date();
                dragDate.setTime(ttask.getDueDate());
                var dlist = new jive.gui.CellDragListener(control, ttask, dragDate, control.notifyStopDrag, control.notifyDragging);
                if($obj(e_obj.monthViewDList) && e_obj.monthViewDList != null){
                    dm.removeDragListener(e_obj, e_obj.monthViewDList);
                }
                e_obj.monthViewDList = dlist;
                dm.enableDrag(e_obj);
                dm.addDragListener(e_obj, dlist);
            }


            /**
             * hide the event if the calendar is hidden
             */
            if(!control.isCalendarVisibleHuh(cp.getProject().getID())){
                jive.ext.x.xDisplayNone(e_obj);
            }else{
                jive.ext.x.xDisplayBlock(e_obj);
            }
        }catch(e){
            alert(e);
        }
    }



    /**
	 * add a task to display on this month view
	 */
	this.addTask = function(ttask){
		try{
			var e_obj = getTaskDOM(ttask);

			var d = getDayCell(ttask.getDueDate());
			d.appendTaskDOM(e_obj);
			// update our parent
			e_obj.myParent = d;

			// update text/checkbox
			e_obj.refresh();

			// update the drag listener
			if($def(control.getDragManager)){
				var dm = control.getDragManager();
				var dragDate = new Date();
				dragDate.setTime(ttask.getDueDate());
				var dlist = new jive.gui.CellDragListener(control, ttask, dragDate, control.notifyStopDrag, control.notifyDragging);
				if($obj(e_obj.monthViewDList) && e_obj.monthViewDList != null){
					dm.removeDragListener(e_obj, e_obj.monthViewDList);
				}
				e_obj.monthViewDList = dlist;
				dm.enableDrag(e_obj);
				dm.addDragListener(e_obj, dlist);
			}


			/**
			 * hide the event if the calendar is hidden
			 */
			if(!control.isCalendarVisibleHuh(ttask.getProjectID())){
				jive.ext.x.xDisplayNone(e_obj);
			}else{
				jive.ext.x.xDisplayBlock(e_obj);
			}
		}catch(e){
			alert(e);
		}
	}

	/**
	 * removes an event from the month view
	 *
	 * this removes any drag listeners from span elements
	 * for that event, and disables the drag
	 */
	this.removeEvent = function(tevent){
		try{
			var settings = control.getSettingsManager();
			// i have completed the drag
			// so remove the listener from
			// all of this events listings in month view
			//
			// i'll be added again by the day cell...
			var arr = getDOMArray(tevent, true);
			// var arr = this.eventDOMHolders.get(event.getId());
			if(jive.ext.y.yArr(arr)){
				for(var j=0;j<arr.length;j++){
					if($def(control.getDragManager())){
						/**
						 * if we don't remove the drag listener
						 * then the event will always think it
						 * started dragging on the wrong day
						 * (the same day really, the day the event
						 *  was on when the page loaded)
						 */
						var dm = control.getDragManager();
						dm.removeDragListener(arr[j], arr[j].monthViewDList);
						dm.disableDrag(arr[j]);
					}
					if($obj(jive.ext.x.xParent(arr[j])) && jive.ext.x.xParent(arr[j]) != null){
						jive.ext.x.xParent(arr[j]).removeChild(arr[j]);
						arr[j].myParent = null;
					}else if($obj(arr[j].myParent) && arr[j].myParent != null){
						arr[j].myParent.removeChild(arr[j]);
						arr[j].myParent = null;
					}
					arr[j].killYourself();
				}
			}
		}catch(e){
			alert("error removing event: " + e);
		}
	}

	/**
	 * removes an event from the month view
	 *
	 * this removes any drag listeners from span elements
	 * for that event, and disables the drag
	 */
	this.removeTask = function(ttask){
		try{
			var settings = control.getSettingsManager();

			var arr = getTaskDOM(ttask);
			// var arr = this.eventDOMHolders.get(event.getId());
			if($obj(arr)){
				/**
				 * if we don't remove the drag listener
				 * then the event will always think it
				 * started dragging on the wrong day
				 * (the same day really, the day the event
				 *  was on when the page loaded)
				 */

				if($def(control.getDragManager)){
					// i have completed the drag
					// so remove the listener from
					// all of this events listings in month view
					//
					// i'll be added again by the day cell...
					var dm = control.getDragManager();
					dm.removeDragListener(arr, arr.monthViewDList);
					dm.disableDrag(arr);
				}

				if($obj(jive.ext.x.xParent(arr)) && jive.ext.x.xParent(arr) != null){
					jive.ext.x.xParent(arr).removeChild(arr);
					arr.myParent = null;
				}else if($obj(arr.myParent) && arr.myParent != null){
					arr.myParent.removeChild(arr);
					arr.myParent = null;
				}
				arr.killYourself();
			}
		}catch(e){
			alert("error removing task: " + ttask.getSubject() + "\nexception: " + e);
		}
	}


	/**
	 * this flushes a calendar entirely. it removes it from teh views,
	 * and also removes any DOM's we have cached for this calendar's
	 * events
	 */
	this.flushCalendar = function(cal){
		var events = that.eventDOMHolders.toArray(jive.gui.isMonthEventDOM);
		for(var i=0;i<events.length;i++){
			if(events[i].getEvent().getCalendarId() == cal.getId()){
				that.flushEvent(events[i].getEvent());
			}
		}
		var tasks = that.taskDOMHolders.toArray(jive.gui.isMonthTaskDOM);
		for(var i=0;i<tasks.length;i++){
			if(tasks[i].getTask().getCalendarId() == cal.getId()){
				that.flushTask(tasks[i].getTask());
			}
		}
	}

	/**
	 * this flushes an event entirely. it removes it from the views,
	 * and also removes any DOM's we have cached for that event
	 */
	this.flushEvent = function(tevent){
		try{
			that.removeEvent(tevent);
			that.eventDOMHolders.clear(tevent.getId());
			// also clear it from teh cache by calendar
			var cal_id = event2cal.get(tevent.getId());
			// clear the task from the old calendar cache
			var calHash = eventDOMbyCalHolder.get(cal_id);
			// we don't check for calHash existing here, b/c it must
			// b/c we added it to the cache in the removeEvent() func
			calHash.clear(tevent.getId());
		}catch(e){
			alert("error flushing event");
		}
	}

	/**
	 * this flushes a task entirely. it removes it from the views,
	 * and also removes any DOM's we have cached for that task
	 */
	this.flushTask = function(ttask){
		try{
			that.removeTask(ttask);
			that.taskDOMHolders.clear(ttask.getID());
			// also clear it from teh cache by calendar
			var cal_id = task2cal.get(ttask.getID());
			// clear the task from the old calendar cache
			var calHash = taskDOMbyCalHolder.get(cal_id);
			// we don't check for calHash existing here, b/c it must
			// b/c we added it to the cache in the getTaskDOM() func
			calHash.clear(ttask.getID());
		}catch(e){
			alert("flushEvent: " + e);
		}
	}


	/**
	 * refresh the text on the bar, possibly because
	 * of a settings change, etc
	 */
	this.refresh = function(){
		var settings = control.getSettingsManager();
		var arr = that.eventDOMHolders.toArray(jive.gui.isMonthEventDOM);
		try{
			for(var ri=0;ri<arr.length;ri++){
				//
				// compare the event against the two timezones,
				// and if teh start/end dates are different,
				// the referesh it
				//
				var tevent = arr[ri].getEvent()
				var old = settings.getOldTimezone();
				if(!jive.model.dateEQ(settings.adjustDate(tevent.getStart()), settings.adjustDate(tevent.getStart(), old)) ||
				   !jive.model.dateEQ(settings.adjustDate(tevent.getEnd()), settings.adjustDate(tevent.getEnd(), old))){
					that.flushEvent(tevent);
					that.addEvent(tevent);
				}
			}
		}catch(e){
			alert(e);
		}

		if(that.isExpandedHuh()){
			that.showMonth(aurora_gui.getCurrentDate());
		}
		that.refreshShading();
	}

	/**
	 * we've just now been placed in our parent div, so
	 * adjust height of our holders etc to fit
	 */
	this.init = function(veryinner){
		veryinner.appendChild(main_panel);
	}

	this.killYourself = function(){
		control = null;
		aurora_gui = null;
	}

	this.refreshWeather = function(){
		// loop through all dates in month view
		// and set weather
		var min = new Date();
		min.setTime(aurora_gui.getMinDate().getTime());

		while(jive.model.dateLTEQ(min, aurora_gui.getMaxDate())){

			var cell = getDayCell(min);
			var image = control.getSettingsManager().getWeatherImage(min);
			var color = cell.style.backgroundColor;
			if(image.length > 0){
				var left = 22;
				if(min.getDate() == 1){
					left = 42;
				}else{
					left = 22;
				}
//				cell.setAttribute("style", "background: url(" + image + ") " + left + "px 2px no-repeat " + cell.style.backgroundColor);
				cell.style.background =  "url(" + image + ") " + left + "px 2px no-repeat " + cell.style.backgroundColor;
			}else{
//				cell.setAttribute("style", "");
				cell.style.background =  "";
			}
			cell.style.backgroundColor = color;

			min.setDate(min.getDate() + 1);
		}

	}



	/**
	 * shows the month view for
	 * Date d
	 * @param d the date of the month to show
	 */
	this.refreshShading = function(){
//		if(loading_state == 1){
			// we've just loaded month view for the first time.
			// so no events/tasks are even in here yet, so don't
			// bother updating shading, when there aren't any
			// events/tasks anyways.
			//
//			return;
//		}
		var settings = control.getSettingsManager();
		var start_on = settings.getStartWeekOn();

		var dtemp = new Date();
		dtemp.setTime(aurora_gui.getMaxDate().getTime());

		var dt = new Date();
		dt.setTime(aurora_gui.getMinDate().getTime());

		// daylight savings time has a problem here...
		// so set the hour to 17, that way daylight savings
		// won't change the date by 2 days if we move 24 hours forward/back
		// temp_panel = this.getDayCellHolder(d);
		dt.setHours(17);
		// now set the date to the sunday (before|that) this month starts
		dt.setDate(1);

		var sub = dt.getDay();
		if(start_on != 0 && sub == 0){
			sub = 7;
		}
		dt.setDate(dt.getDate() - sub + start_on);

		// this will help us as we iterate through the days
		// it will keep track of what month the sunday for that
		// month is
		// this way, we can bail on the while loop once we've
		// finished showing this month
		var currMonth = new Date();
		currMonth.setTime(dt.getTime());

		var backgrounds = new Array();
		backgrounds[0] = "#FFFFFF";
		backgrounds[1] = "#EFEFEF";
		backgrounds[2] = "#DFDFDF";
		backgrounds[3] = "#CFCFCF";
		backgrounds[4] = "#BFBFBF";

		var now = settings.getNOW();

		var smart_shading = settings.getSmartShading();
		var current_month = aurora_gui.getCurrentDate().getMonth();
		// add all the day cells
		while(currMonth.getMonth() <= dtemp.getMonth() && currMonth.getYear() == dtemp.getYear() || currMonth.getYear() < dtemp.getYear()){
			var cell = getDayCell(dt);
			var dt_is_today = jive.model.dateEQ(dt, now);
			if(smart_shading){
                var true_num = cell.countVisibleItems();

				if(true_num > 4){
					true_num = 4;
				}else if(true_num < 0){
					true_num = 0;
				}
				if(dt_is_today){
					cell.style.backgroundColor = "#e4f6e7";
					cell.outColor = "#e4f6e7";
				}else{
					cell.style.backgroundColor = backgrounds[true_num];
					cell.outColor = backgrounds[true_num];
				}
			}else if(cell.getDate().getMonth() != current_month){
				cell.style.backgroundColor = backgrounds[2];
				cell.outColor = backgrounds[2];
			}else{
				cell.style.backgroundColor = backgrounds[0];
				cell.outColor = backgrounds[0];
			}
			if(dt_is_today){
				cell.style.backgroundColor = "#e4f6e7";
				cell.setAttribute("class", "month_cell month_today_cell");
				cell.className = "month_cell month_today_cell";
				cell.overColor = "#ffffda";
			}else{
				cell.setAttribute("class", "month_cell month_day_cell");
				cell.className = "month_cell month_day_cell";
//				cell.overColor = "#f0f6fc";
				cell.overColor = "#ffffda";
			}

			dt.setDate(dt.getDate() + 1);
			if(dt.getDay() == 0){
				currMonth.setTime(dt.getTime());
			}
		}
		that.refreshWeather();
	}



	/**
	 * toggle event visibility if
	 * the calendar visibility is switched (ie, in the sidebar)
	 */
	this.calendarVisible = function(calendar, visibleHuh){
		var show_tasks_huh = control.getSettingsManager().getShowTasks();
		var objs = taskDOMbyCalHolder.get(calendar.getId());
		if($obj(objs) && objs != null){
			objs = objs.toArray();
			for(var i=0;i<objs.length;i++){
				if(visibleHuh && show_tasks_huh){
					jive.ext.x.xDisplayBlock(objs[i]);
				}else{
					jive.ext.x.xDisplayNone(objs[i]);
				}
			}
		}
		var objs = eventDOMbyCalHolder.get(calendar.getId());
		if($obj(objs) && objs != null){
			objs = objs.toArray();
			for(var i=0;i<objs.length;i++){
				for(var j=0; j<objs[i].length; j++){
					if(visibleHuh){
						jive.ext.x.xDisplayBlock(objs[i][j]);
					}else{
						jive.ext.x.xDisplayNone(objs[i][j]);
					}
				}
			}
		}
		that.refreshShading();
	}

	/**
	 * notify everybody else that a drag/drop happened
	 */
	this.stopDrag = function(tevent, dt, left, top){
		var doneHuh = false;
		if(that.isExpandedHuh()){
			/**
			 * loop through table cells
			 * if a point matches, then drop it in it's day cell
			 */
			if(main_panel.childNodes.length > 0){
				var table = main_panel.childNodes[0];
				for(var i=1;i<table.childNodes.length;i++){
					tr = table.childNodes[i];
					for(var j=0;j<tr.childNodes.length;j++){
						if(tr.childNodes[j].childNodes.length > 0){
							var day = tr.childNodes[j];
							if(jive.ext.x.xHasPoint(day, left, top)){
								if($def(day.dropPoint)){
									day.dropPoint(tevent,dt);
									doneHuh = true;
								}
							}
						}
					}
				}
			}
		}
		return doneHuh;
	}

	// this is the last day cell that
	// we've hovered over when dragging
	// an event or task
	var hovered_day_cell = null;
	var threadNum = 0;
	this.dragging = function(tevent, dt, left, tp){
		// this function will be called as they drag around an event
		// which means it'll be called probably as its running
		//
		// the threadNum and myThread variables will make sure that
		// I drop out of execution asap as a new thread starts.
		threadNum++;
		var myThread = threadNum;

		// track if we show a drop zone or not
		var zonedHuh = false;

		// let's check our currently hovered day cell
		// to see if that's what we're still hovered over.
		// if we're not over the same cell anymore, lets
		// just check each cell sequentially.
		//
		// later, we can optimize this to check nearby cells
		// first, instead of just checking /left->bottom/right
		//
		if(hovered_day_cell != null){
			if(jive.ext.x.xHasPoint(hovered_day_cell, left, tp)){
				var w = Math.floor(.95 * jive.ext.x.xWidth(hovered_day_cell));
				control.showHoverOver(jive.ext.x.xPageX(hovered_day_cell), jive.ext.x.xPageY(hovered_day_cell), w, jive.ext.x.xHeight(hovered_day_cell));
				zonedHuh = true;
			}
		}
		// loop through all dates in month view
		// and set weather
		if(main_panel.childNodes.length > 0 && !zonedHuh){
			var table = main_panel.childNodes[0];
			for(var i=1;i<table.childNodes.length && myThread == threadNum && !zonedHuh;i++){
				tr = table.childNodes[i];
				for(var j=0;j<tr.childNodes.length && myThread == threadNum && !zonedHuh;j++){
					var day = tr.childNodes[j];
					if(jive.ext.x.xHasPoint(day, left, tp)){
						if($def(day.dropPoint)){
							// hooray!
							// move the drag area here
							// and save this cell as
							// the last hovered
							var w = Math.floor(.95 * jive.ext.x.xWidth(day));
							control.showHoverOver(jive.ext.x.xPageX(day), jive.ext.x.xPageY(day), w, jive.ext.x.xHeight(day));
							hovered_day_cell = day;
							zonedHuh = true;
						}
					}
				}
			}
			if(myThread != threadNum){
				return;
			}
		}
		if(!zonedHuh){
			control.hideHover();
		}
		return zonedHuh;
	}


	/**********************************************************************
	***********************************************************************
	***********************************************************************
	**
	** END VIEW INTERFACE TO MONTH_VIEW
	**
	***********************************************************************
	***********************************************************************
	***********************************************************************/

	this.fixHeight = function(for_rows){
		try{
//			alert("updating height: " + for_rows);
			jive.ext.x.xHeight(main_panel, for_rows);
			var for_rows = for_rows - 20; // subtract 20 b/c of the header row
			if(main_panel.childNodes.length > 0){
				var table = main_panel.childNodes[0];
				for_rows += for_rows % (table.childNodes.length - 1);
				for(var i=1;i<table.childNodes.length;i++){
					var tr = table.childNodes[i];
					jive.ext.x.xHeight(tr, Math.floor(for_rows / (table.childNodes.length - 1)));
					if(jive.ext.x.xIE4Up){
//						alert("updating foo!");
						for(var j=0;j<tr.childNodes.length;j++){
							jive.ext.x.xDisplayNone(tr.childNodes[j]);
							jive.ext.x.xDisplayBlock(tr.childNodes[j]);
						}
					}
				}
			}
		}catch(e){
			alert(e);
		}
	}

	/**
	 * gets the events for a specific day
	 * @param dt a date object
	 */
	this.getEventsOn = function(dt){
		// we're going to load the day cell for this event
		// and get the events out of that
		var events = new Array();
		var cell = getDayCell(dt);
		for(var i=1; i < cell.childNodes.length; i++){
			if($def(cell.childNodes[i].getEvent)){
				events.push(cell.childNodes[i].getEvent());
			}
		}
		return events;
	}

	/**
	 * gets the tasks for a specific day
	 * @param dt a Date object
	 */
	this.getTasksOn = function(dt){
		// we're going to load the day cell for this event
		// and get the events out of that
		var tasks = new Array();
		var cell = getDayCell(dt);
		for(var i=1; i < cell.childNodes.length; i++){
			if($def(cell.childNodes[i].getTask)){
				tasks.push(cell.childNodes[i].getTask());
			}
		}
		return tasks;
	}


	/**
	 * gets an array of dom objects for the event
	 * there will be enough dom objects to have 1 per day
	 * that the event spans
	 */
	function getDOMArray(tevent, skip){

		try{
			var settings = control.getSettingsManager();

			//
			// we store 1 DOM entry for each day the event is on
			//
			// however, if the dom entry would extend past teh min or max date
			// in month view, then we only cache the necessary dom entries.
			//
			// so if we're already caching the dom array, then lets make sure
			// that we're caching all of it. it could be that we're caching this months,
			// but we just loaded a whole new month and extended max date, so we need
			// to load in more dom's onto the array.
			//
			// if nothing is in the cache yet, then lets load in enough dom's
			// to either take care of the entire event's duration, or extend
			// until min or max date, whichever is shorter
			//
			//

			var iter = new Date();
			var end_iter = new Date();
			if(tevent.isAllDay()){
				iter.setTime(tevent.getStart().getTime());
				end_iter.setTime(tevent.getEnd().getTime());
			}else{
				iter.setTime(settings.adjustDate(tevent.getStart()).getTime());
				end_iter.setTime(settings.adjustDate(tevent.getEnd()).getTime());
			}

			//
			// iter is the start of the event
			// end_iter is the end of the event
			//
			// now lets make sure that iter >= minDate and end_iter <= maxDate
			//

			if(jive.model.dateLT(iter, control.getEventCache().getMinTime())){
				// iter is smaller, so change iter to min date
				iter.setTime(control.getEventCache().getMinTime().getTime());
			}
			if(jive.model.dateGT(end_iter, control.getEventCache().getMaxTime())){
				// end_iter is too large, so change end_iter to max date
				end_iter.setTime(control.getEventCache().getMaxTime().getTime());
			}
		}catch(e){
			alert("top of getdomarray: " + e);
		}

		try{

			var e_obj = that.eventDOMHolders.get(tevent.getId());
			if(!$obj(e_obj)){
				if($def(skip) && skip){
					return null;
				}
				e_obj = new Array();
				e_obj.getEvent = function(){ return tevent; }
				that.eventDOMHolders.put(tevent.getId(), e_obj);

				event2cal.put(tevent.getId(), tevent.getCalendarId());
				var calHash = eventDOMbyCalHolder.get(tevent.getCalendarId());
				if(!$obj(calHash) || calHash == null){
					calHash = new jive.ext.y.HashTable();
					eventDOMbyCalHolder.put(tevent.getCalendarId(), calHash);
				}
				calHash.put(tevent.getId(), e_obj);
			}

			var i=0;
			while(jive.model.dateLTEQ(iter, end_iter)){
				if(e_obj.length <= i){

					var formatDate = function(d){
						return function(d2){
							var dh = new jive.model.DateHelper(control);
							if(jive.model.dateEQ(settings.adjustDate(d2), d)){
								return dh.formatTo12HourTime(settings.adjustDate(d2));
							}else{
								return dh.formatToShortDate(settings.adjustDate(d2));
							}
						}
					}(iter);

					var txt = control.getEventDOMFactory().getEventDOM(tevent, aurora_gui.notifyEventClicked, aurora_gui.notifyEventDblClicked, formatDate);
					txt.showTimes(false);
					// make a field to store our parent DOM node
					// it's false b/c we don't have a parent yet.
					txt.getDOM().myParent = null;
					txt.getDOM().killYourself = txt.killYourself;
					txt.getDOM().refresh = txt.refresh;
					txt.getDOM().lighten = txt.lighten;
					txt.getDOM().darken = txt.darken;

					txt = txt.getDOM();

					e_obj[i] = txt;
				}else{
					e_obj[i].refresh();
				}

				i++;
				iter.setDate(iter.getDate() + 1);
			}
			while(e_obj.length > i){
				if($obj(jive.ext.x.xParent(e_obj[i])) && jive.ext.x.xParent(e_obj[i]) != null){
					jive.ext.x.xParent(e_obj[i]).removeChild(e_obj[i]);
					e_obj[i].myParent = null;
				}else if($obj(e_obj[i].myParent) && e_obj[i].myParent != null){
					e_obj[i].myParent.removeChild(e_obj[i]);
					e_obj[i].myParent = null;
				}
				e_obj[i].killYourself();
				e_obj.splice(i,1);
			}

			for(var i=0;i<e_obj.length;i++){
				if(that.getItemVisibility()){
					e_obj[i].style.visibility = "visible";
				}else{
					e_obj[i].style.visibility = "hidden";
				}
			}

			return e_obj;
		}catch(e){
			alert("getting array dom in month: " + e);
		}
	}

    var num_weeks = 2;
    this.setNumWeeks = function(foo){
        num_weeks = foo;
    }



    /**
	 * shows the month view for
	 * Date d
	 * @param d the date of the month to show
	 */
	var last_month_min = null;
	var last_month_max = null;

	var loading_state = 0;
	this.showMonth = function(dtemp){
		var settings = control.getSettingsManager();
		var start_on = settings.getStartWeekOn();

		loading_state++;

		expanded = true;

		var d = new Date();
		d.setTime(dtemp.getTime());
		// daylight savings time has a problem here...
		// so set the hour to 17, that way daylight savings
		// won't change the date by 2 days if we move 24 hours forward/back
		// temp_panel = this.getDayCellHolder(d);
		d.setHours(17);
        start_on = d.getDay();

        // now set the date to the sunday (before|that) this month starts
//		d.setDate(1);
//		var sub = d.getDay();
//		if(start_on != 0 && sub == 0){
//			sub = 7;
//		}
//		d.setDate(d.getDate() - sub + start_on);

		// this will help us as we iterate through the days
		// it will keep track of what month the sunday for that
		// month is
		// this way, we can bail on the while loop once we've
		// finished showing this month
		var currMonth = new Date();
		currMonth.setTime(dtemp.getTime());

		var lang = control.getLanguageManager().getActiveLanguage();
		var affectedTasks = new Array();
		if(force_month_view || last_month_min == null || !jive.model.dateEQ(last_month_min, d)){
			force_month_view = false;
			/**
			 * set up the minimum date
			 */
			var currMin = new Date();
			currMin.setTime(d.getTime());
			aurora_gui.setMinDate(currMin);

			// add all the day cells
			var cell;
			var td;
			var tr;
			var table = document.createElement('DIV');
			table.setAttribute("class", "month_table");
			table.className = "month_table";
			while(main_panel.childNodes.length > 0) main_panel.removeChild(main_panel.childNodes[0]);

			tr = document.createElement('DIV');
			table.appendChild(tr);
			jive.ext.x.xHeight(tr, 20);

			for(var i=start_on;i-start_on<7;i++){
				td = document.createElement('DIV');
				tr.appendChild(td);
				td.setAttribute("class", "month_table_th");
				td.className = "month_table_th";
				if(jive.ext.x.xWidth(main_panel) > 640){
					td.appendChild(document.createTextNode(lang.longDay(i%7)));
				}else{
					td.appendChild(document.createTextNode(lang.shortDay(i%7)));
				}
				td.style.left = ((i-start_on) * 14.2857) + "%";
			}

			var rows = new Array();
			var weekday_num = 0;
            for(var i=0;i < num_weeks * 7;i++){
//            while(currMonth.getMonth() <= dtemp.getMonth() && currMonth.getYear() == dtemp.getYear() || currMonth.getYear() < dtemp.getYear()){
				if(d.getDay() == start_on){
					tr = document.createElement('DIV');
					tr.setAttribute("class","month_table_row");
					tr.className = "month_table_row";
					rows.push(tr);
					weekday_num = 0;
				}
				td = getDayCell(d);
				td.updateText();
				td.over = false;
				tr.appendChild(td);
				td.style.left = (weekday_num * 14.2857) + "%";
				weekday_num++;


				var foo = new Date();
				foo.setTime(d.getTime());
				d = foo;
				d.setDate(d.getDate() + 1);
				if(d.getDay() == start_on){
					currMonth.setTime(d.getTime());
				}
				if(jive.ext.x.xIE4Up){
					// IE has a bug where it doesn't preserve checkbox's on/off state
					// so we need to fix it here...
					var tasks = that.getTasksOn(d);
					affectedTasks = affectedTasks.concat(tasks);
				}
			}
			for(var i=0;i<rows.length;i++){
				table.appendChild(rows[i]);
			}
			/**
			 * set up the maximum date
			 */
			aurora_gui.setMaxDate(d);

			var currDate = new Date();
			currDate.setTime(dtemp.getTime());
			currDate.setHours(17);
			aurora_gui.setCurrentDate(currDate);

			var lang = control.getLanguageManager().getActiveLanguage();

//			if(!jive.model.dateLTEQ(last_month_min, aurora_gui.getMinDate()) || !jive.model.dateGTEQ(last_month_max, aurora_gui.getMaxDate())){
//				// refresh the shading
//				that.notifyTimesChanged(aurora_gui.getMinDate(), aurora_gui.getMaxDate());
//			}

			main_panel.appendChild(table);

			for(var i=1;i<table.childNodes.length;i++){
				var tr = table.childNodes[i];
//				if($def(tr.style.height)) tr.style.height = (100/(table.childNodes.length-1) - .1) + "%";
				for(var j=0;j<tr.childNodes.length;j++){
					var cell = tr.childNodes[j];
					if(cell.childNodes.length > 0){
						jive.ext.x.xZIndex(cell.childNodes[0], 10 + i);
					}
				}
			}

			last_month_min = new Date();
			last_month_max = new Date();
			last_month_min.setTime(aurora_gui.getMinDate().getTime());
			last_month_max.setTime(aurora_gui.getMaxDate().getTime());
		}else{
			if(jive.ext.x.xIE4Up){
				while(currMonth.getMonth() <= dtemp.getMonth() && currMonth.getYear() == dtemp.getYear() || currMonth.getYear() < dtemp.getYear()){
					var foo = new Date();
					foo.setTime(d.getTime());
					d = foo;
					d.setDate(d.getDate() + 1);
					if(d.getDay() == start_on){
						currMonth.setTime(d.getTime());
					}
					// IE has a bug where it doesn't preserve checkbox's on/off state
					// so we need to fix it here...
					var tasks = that.getTasksOn(d);
					affectedTasks = affectedTasks.concat(tasks);
				}
			}
			aurora_gui.setMinDate(last_month_min);
			aurora_gui.setMaxDate(last_month_max);
		}

		// IE has a bug where it doesn't preserve checkbox's on/off state
		// so we need to fix it here...
		if(jive.ext.x.xIE4Up){
			for(var i=0;i<affectedTasks.length;i++){
				var dom = getTaskDOM(affectedTasks[i]);
				dom.refresh();
			}
		}
		that.refreshShading();
		var t = aurora_gui.getFilterText();
		that.filter(t);
		aurora_gui.fixHeight();
	}


	/**
	 * unselects teh current event in the view
	 */
	function unselectAll(){
		if(jive.model.isEvent(aurora_gui.getSelectedItem())){
			var e_obj = getDOMArray(aurora_gui.getSelectedItem());
			for(var i=0;i<e_obj.length;i++){
				filterNode(e_obj[i], aurora_gui.getFilterText());
			}
		}
		if(jive.model.isTask(aurora_gui.getSelectedItem())){
			var e_obj = getTaskDOM(aurora_gui.getSelectedItem());
			filterNode(e_obj, aurora_gui.getFilterText());
		}
	}


	function ensureStartDates(){
		var start_on = control.getSettingsManager().getStartWeekOn();
		var dtemp = aurora_gui.getCurrentDate();
		var d = new Date();
		d.setTime(dtemp.getTime());
		// daylight savings time has a problem here...
		// so set the hour to 17, that way daylight savings
		// won't change the date by 2 days if we move 24 hours forward/back
		// temp_panel = this.getDayCellHolder(d);
		d.setHours(17);

		// now set the date to the sunday (before|that) this month starts
		d.setDate(1);
		var sub = d.getDay();
		if(start_on != 0 && sub == 0){
			sub = 7;
		}
		d.setDate(d.getDate() - sub + start_on);

		// this will help us as we iterate through the days
		// it will keep track of what month the sunday for that
		// month is
		// this way, we can bail on the while loop once we've
		// finished showing this month
		var currMonth = new Date();
		currMonth.setTime(dtemp.getTime());


		aurora_gui.setMinDate(d);
		while(currMonth.getMonth() <= dtemp.getMonth() && currMonth.getYear() == dtemp.getYear() || currMonth.getYear() < dtemp.getYear()){
			var foo = new Date();
			foo.setTime(d.getTime());
			d = foo;
			d.setDate(d.getDate() + 1);
			if(d.getDay() == start_on){
				currMonth.setTime(d.getTime());
			}
		}
		var currMax = new Date();
		currMax.setTime(d.getTime());
		currMax.setDate(currMax.getDate() + 1);
		aurora_gui.setMaxDate(currMax);

		var currDate = new Date();
		currDate.setTime(dtemp.getTime());
		currDate.setHours(17);
		aurora_gui.setCurrentDate(currDate);

		last_month_min = new Date();
		last_month_max = new Date();
		last_month_min.setTime(aurora_gui.getMinDate().getTime());
		last_month_max.setTime(aurora_gui.getMaxDate().getTime());

		force_month_view = true;
	}

	function updateShowTasks(){
		// show/hide tasks based on preference
		var show_huh = control.getSettingsManager().getShowTasks();
		if(show_huh){
			// loop through calendars,
			// and if the calendar is visible, then
			// get all the task doms in that calendar
			// and displayBlock
			var cals = control.getCalendarCache().getCalendars();
			for(var i=0;i<cals.length;i++){
				if(control.isCalendarVisibleHuh(cals[i].getId())){
					var calDomHash = taskDOMbyCalHolder.get(cals[i].getId());
					if($obj(calDomHash)){
						// displayBlock everything
						var tasks = calDomHash.toArray();
						for(var j=0; j<tasks.length; j++){
							if($def(tasks[j].getTask)){
								jive.ext.x.xDisplayBlock(tasks[j]);
							}
						}
					}
				}
			}
		}else{
			// displayNone everything
			var tasks = that.taskDOMHolders.toArray(jive.gui.isMonthTaskDOM);
			for(var i=0; i<tasks.length; i++){
				if($def(tasks[i].getTask)){
					jive.ext.x.xDisplayNone(tasks[i]);
				}
			}
		}
	}

	/************************************************
	 * listen to stuff
	 ************************************************/

	/**
	 * add a listener to listen for event clicks
	 * when an event is clicked, highlight it in
	 * the main view, and unhighlight (if needed)
	 * the previously highlighted event
	 */
	var list = new Object();
	list.eventClicked = function(tevent){
		unselectAll();
		// select the event
		var e_obj = getDOMArray(tevent);
		if($obj(e_obj)){
			for(var i=0;i<e_obj.length;i++){
				e_obj[i].setAttribute("class","month_day_cell_item_highlight");
				e_obj[i].className = "month_day_cell_item_highlight";
			}
		}
	}
	list.eventDblClicked = function(tevent){ }
	list.taskClicked = function(ttask){
		unselectAll();
		// select the task
		var e_obj = getTaskDOM(ttask);
		if($obj(e_obj)){
			e_obj.setAttribute("class","month_day_cell_item_highlight");
			e_obj.className = "month_day_cell_item_highlight";
		}
	}
	list.taskDblClicked = function(ttask){ }
	list.unselectAll = function(){
		unselectAll();
	}
	aurora_gui.addEventListener(list);

	/**
	 * listen to event cache
	 * when we load an event, tell the month view
	 */
    var list = new jive.model.ProjectCacheListener();
    list.loadProject = function(p){
        var cps = p.getCheckPoints();
        for(var i=0;i<cps.length;i++){
            that.addCheckPoint(cps[i]);
        }
    }
    control.getProjectCache().addListener(list);




    var list = new jive.model.TaskCacheListener();
	list.loadTask = function(ttask){
		if(ttask.hasDueDate()){
			that.addTask(ttask);
		}
	}
	list.doneLoadingTasks = function(){
		// refresh the shading
		that.refreshShading();
	}
    list.taskChanged = function(ttask){
        // refresh the shading
        that.refreshShading();

    }

	list.savingTask = function(ttask){
		var obj = getTaskDOM(ttask);
		obj.setDisabled(true);
	}
	list.doneSavingTask = function(ttask){
		// refresh the shading
		loading_state++;
		that.refreshShading();
		if(ttask.hasDueDate()){
			var obj = getTaskDOM(ttask);
			obj.refresh();
			obj.setDisabled(false);
		}
		// also, udpate it's cache by calendar id
		var old_cal = task2cal.get(ttask.getID());
		if(old_cal != ttask.getProjectID()){
			// clear the task from the old calendar cache
			var calHash = taskDOMbyCalHolder.get(old_cal);
			calHash.clear(ttask.getID());
			// add it to the correct calendar cache
			var calHash = taskDOMbyCalHolder.get(ttask.getProjectID());
			if(!$obj(calHash) || calHash == null){
				calHash = new jive.ext.y.HashTable();
				taskDOMbyCalHolder.put(ttask.getProjectID(), calHash);
			}
			calHash.put(ttask.getID(), obj);
		}
	}
	list.savingTaskFailed = function(ttask){
		// refresh the shading
		that.refreshShading();
		var obj = getTaskDOM(ttask);
		obj.setDisabled(false);
		obj.setChecked(ttask.getStatus() == "Complete");
	}
	list.deletingTask = function(ttask){
		// refresh the shading
		that.refreshShading();
	}
	list.doneDeletingTask = function(ttask){
		// refresh the shading
		that.refreshShading();
	}
	list.deletingTaskFailed = function(ttask){
		// refresh the shading
		that.refreshShading();
	}
	list.deletingTaskSeries = function(ttask){
		// refresh the shading
		that.refreshShading();
	}
	list.doneDeletingTaskSeries = function(ttask){
		// refresh the shading
		that.refreshShading();
	}
	list.deletingTaskSeriesFailed = function(ttask){
		// refresh the shading
		that.refreshShading();
	}
	control.getTaskCache().addListener(list);
	/*************************************************
	 * last bit of initialization
	 *************************************************/
	ensureStartDates();
	jive.ext.x.xDisplayNone(main_panel);
}



jive.gui.MonthDayCell = function(control, aurora_gui, month_view, dtemp){

    var cell = new jive.gui.MonthDayGroupedCell(control, aurora_gui, month_view, dtemp);
    var day = cell.getDOM();


    this.getTasks = day.getTasks;

    /**
	 * add a function to the day cell
	 * that lets it add events
	 *
	 * this will add the event into sorted position
	 * into this day cell
	 */
	day.appendTaskDOM = function(txt){
		var txt_title = txt.getTask().getSubject().toLowerCase();
		for(var i=0;i<day.childNodes.length;i++){
			if($def(day.childNodes[i].getEvent)){
				day.insertBefore(txt, day.childNodes[i]);
				break;
			}else if($def(day.childNodes[i].getTask)){
				if(day.childNodes[i].getTask().getSubject().toLowerCase() > txt_title){
					day.insertBefore(txt, day.childNodes[i]);
					break;
				}
			}
		}
		if(i == day.childNodes.length){
			day.appendChild(txt);
		}
	};
    this.appendTaskDOM = day.appendTaskDOM;

    day.removeTaskDOM = function(txt){
       day.removeChild(txt);
    }
    this.removeTaskDOM = day.removeTaskDOM;

    day.getTasks = function(){
        var cell = day;
        var tasks = new Array();
        for(var i=1; i < cell.childNodes.length; i++){
			if($def(cell.childNodes[i].getTask)){
				tasks.push(cell.childNodes[i].getTask());
			}
		}
		return tasks;
    }

	this.getDOM = function(){
		return day;
	}
}


jive.gui.MonthDayGroupedCell = function(control, aurora_gui, month_view, dtemp){

    var that = this;

    var jiveprojecttooltip;

	var tlang = control.getLanguageManager().getActiveLanguage();
	var day = document.createElement('DIV');

	var str = dtemp.getDate();
	if(dtemp.getDate() == 1){
		str = tlang.shortMonth(dtemp.getMonth()) + " " + str;
	}


    var tcache = new jive.ext.y.HashTable();
    var cpcache = new jive.ext.y.HashTable();

    var number = document.createElement('SPAN');
	number.getDate = function(dt){ return function(){ return dt;}; }(dtemp);
	number.setAttribute("class","month_day_cell_number");
	number.className = "month_day_cell_number";

	var add_link = document.createElement('SPAN');
	add_link.id = "add_link";
	add_link.setAttribute("class","month_day_cell_number_link");
	add_link.className = "month_day_cell_number_link";
	add_link.appendChild(document.createTextNode("[ + ]"));

	jive.ext.x.xDisplayNone(add_link);
	number.appendChild(add_link);
	number.appendChild(document.createTextNode(str));


    day.appendChild(number);




    var cp_count = 0;
    var cp_count_div = document.createElement('DIV');
    cp_count_div.href = "javascript:;"
    cp_count_div.className = "jive-link-checkpoint jiveTT-hover-tasks";
    cp_count_div.setAttribute("class","jive-link-checkpoint jiveTT-hover-tasks");
    day.appendChild(cp_count_div);
    jive.ext.x.xDisplayNone(cp_count_div);

    var task_count = 0;
    var task_count_div = document.createElement('DIV');
    task_count_div.href = "javascript:;"
    task_count_div.className = "jive-link-task jiveTT-hover-tasks";
    task_count_div.setAttribute("class","jive-link-task jiveTT-hover-tasks");
    day.appendChild(task_count_div);
    jive.ext.x.xDisplayNone(task_count_div);


    jive.ext.x.xAddEventListener(task_count_div, "mouseover", function(dtemp){
        return function(e){
//            jiveprojecttooltip.getTasksTooltip(dtemp.getTime());

            var dom = jiveprojecttooltip.getDOM();
            while(dom.childNodes.length > 0) dom.removeChild(dom.childNodes[0]);

            var title = document.createElement("strong");
            var s = (task_count_div != 1) ? "s" : "";
            var dh = new jive.model.DateHelper(control);
            title.appendChild(document.createTextNode(dh.formatToMedDate(that.getDate()) + ", " + that.getDate().getFullYear() + " - " + task_count + " task" + s));
            var ul = document.createElement('UL');

            var tasks = that.getTasks();
            for(var i=0;i<tasks.length;i++){
                var ttask = tasks[i];
                var li = document.createElement('LI');
                var avatar = document.createElement('A');
                avatar.className = "jiveTT-hover-user jive-username-link";
                avatar.href = ttask.getAssignedTo().getURL();
                var img = document.createElement('IMG');
                img.className = "jive-avatar";
                img.setAttribute("class","jive-avatar");
                img.src = CS_BASE_URL + "/people/" + ttask.getAssignedTo().getUsername() + "/avatar/22.png";
                avatar.appendChild(img);
                var span = document.createElement('SPAN');
                var name = document.createElement('A');
                name.href = ttask.getAssignedTo().getURL();
                name.className = "jiveTT-hover-user jive-username-link";
                name.setAttribute("class","jiveTT-hover-user jive-username-link");
                name.appendChild(document.createTextNode(ttask.getAssignedTo().getFullName()));

                jive.ext.x.xAddEventListener(name, "mouseover", function(id){
                    return function(){
                        quickuserprofile.getUserProfileTooltip(id);
                    }
                }(ttask.getAssignedTo().getID()));
                jive.ext.x.xAddEventListener(name, "mouseout", function(){
                        quickuserprofile.cancelTooltip();
                });

                var task = document.createElement('A');
                task.href = ttask.getURL();
                task.appendChild(document.createTextNode(ttask.getSubject()));
                span.appendChild(task);
                span.appendChild(document.createTextNode(" : "));
                span.appendChild(name);
                li.appendChild(avatar);
                li.appendChild(span);
                ul.appendChild(li);
            }


            dom.appendChild(title);
            dom.appendChild(ul);
        }
    }(dtemp));

    jive.ext.x.xAddEventListener(cp_count_div, "mouseover", function(dtemp){
        return function(e){
//            jiveprojecttooltip.getTasksTooltip(dtemp.getTime());

            var dom = jiveprojecttooltip.getDOM();
            while(dom.childNodes.length > 0) dom.removeChild(dom.childNodes[0]);

            var title = document.createElement("strong");
            var s = (cp_count != 1) ? "s" : "";
            var dh = new jive.model.DateHelper(control);
            title.appendChild(document.createTextNode(dh.formatToMedDate(that.getDate()) + ", " + that.getDate().getFullYear() + " - " + cp_count + " checkpoint" + s));
            var ul = document.createElement('UL');
            var cps = that.getCheckPoints();
            for(var i=0;i<cps.length;i++){
                var cp = cps[i];
                var li = document.createElement('LI');
                var span = document.createElement('SPAN');
                span.id = "jive-note-checkpoint-body";
                var name = document.createElement('STRONG');
                name.className = "jive-link-checkpoint";
                name.setAttribute("class","jive-link-checkpoint");
                name.appendChild(document.createTextNode(cp.getName()));
                span.appendChild(name);
                if(cp.getProject().isEditable()){
                    var p = document.createElement('P');

                    var edit = document.createElement('A');
                    edit.href = CS_BASE_URL + "/edit-checkpoint!input.jspa?project=" + cp.getProject().getID() + "&checkPointID=" + cp.getID();
                    edit.appendChild(document.createTextNode("Edit"));
                    var del = document.createElement('A');
                    del.href = CS_BASE_URL + "/delete-checkpoint!input.jspa?project=" + cp.getProject().getID() + "&checkPointID=" + cp.getID();
                    del.appendChild(document.createTextNode("Delete"));
                    p.appendChild(edit);
                    p.appendChild(del);
                    span.appendChild(p);
                }
                li.appendChild(span);
                ul.appendChild(li);
            }


            dom.appendChild(title);
            dom.appendChild(ul);
        }
    }(dtemp));

    day.mouseover = function(){
		if(typeof(jive) != "undefined"){
			try{
				if(!day.overed){
					day.outColor = day.style.backgroundColor;
				}
				if(!control.isReadOnly() && month_view.getItemVisibility() && month_view.hasAddView()){
					jive.ext.x.xDisplayBlock(add_link);
				}
				day.style.backgroundColor = day.overColor;
				day.overed = true;
			}catch(e){ }
		}
	};
	day.mouseout = function(){
		if(typeof(jive) != "undefined"){
			try{
				jive.ext.x.xDisplayNone(add_link);
				day.style.backgroundColor = day.outColor;
				day.overed = false;
			}catch(e){ }
		}
	};
	day.updateText = function(){
		var tlang = control.getLanguageManager().getActiveLanguage();
		var str = dtemp.getDate();
		if(dtemp.getDate() == 1){
			var add_link = number.childNodes[0];
			str = tlang.shortMonth(dtemp.getMonth()) + " " + str;
			while(number.childNodes.length > 0) number.removeChild(number.childNodes[0]);
			number.appendChild(add_link);
			number.appendChild(document.createTextNode(str));
		}
	};
	day.getDate = function(d){ return function(){ return d; } }(dtemp);
	day.dropPoint = function(tevent, dt){

		if(dt != null){
			var d = new Date();
			d.setTime(day.getDate().getTime());
			d.setHours(dt.getHours());
			d.setMinutes(dt.getMinutes());
			d.setSeconds(dt.getSeconds());
			d.setMilliseconds(dt.getMilliseconds());
			var distance = d.getTime() - dt.getTime();
		}

		if(dt != null && distance != 0 && jive.model.isEvent(tevent)){
			var s = new Date();
			s.setTime(tevent.getStart().getTime());
			var e = new Date();
			e.setTime(tevent.getEnd().getTime());

			var durr = e.getTime() - s.getTime();
			s.setTime(s.getTime() + distance);
			e.setTime(s.getTime() + durr);
			tevent.setStart(s);
			tevent.setEnd(e);

			// when we edit an event, we need to
			// 'confirm' the changes.
			// this fires the eventChanged notice
			// so that the listeners can actually
			// save the change to the DB
			tevent.confirm();
		}else if((dt == null || distance != 0) && jive.model.isTask(tevent)){
			// now change it's time
			if(dt != null){
				var d = new Date();
				d.setTime(tevent.getDueDate().getTime() + distance);
				tevent.setDueDate(d);
			}else{
				var d = new Date();
				d.setTime(day.getDate().getTime());
				tevent.setDueDate(d);
			}

			// when we edit an task, we need to
			// 'confirm' the changes.
			// this fires the eventChanged notice
			// so that the listeners can actually
			// save the change to the DB
			tevent.confirm();
		}
	};

	/**
	 * add a function to the day cell
	 * that lets it add events
	 *
	 * this will add the event into sorted position
	 * into this day cell
	 */
	day.appendTaskDOM = function(txt){
        // cache it.
        tcache.put(txt.getTask().getID(), txt.getTask())

        jiveprojecttooltip = new JiveProjectTooltip(txt.getTask().getProjectID(), "jive-note-checkpoint-body", "jive-note-tasks-body", "", "", "", "", "");
        jive.ext.x.xAddEventListener(task_count_div, "mouseout", jiveprojecttooltip.cancelTooltip);

        task_count++;
        jive.ext.x.xDisplayNone(txt);
        while(task_count_div.childNodes.length > 0) task_count_div.removeChild(task_count_div.childNodes[0]);
        task_count_div.appendChild(document.createTextNode(task_count + " tasks"));
        jive.ext.x.xDisplayBlock(task_count_div);
    };
    this.appendTaskDOM = day.appendTaskDOM;

    day.removeTaskDOM = function(txt){
        tcache.clear(txt.getTask().getID());

        task_count--;
        if(task_count == 0){
            jive.ext.x.xDisplayNone(task_count_div);
        }
    }
    this.removeTaskDOM = day.removeTaskDOM;

    day.getTasks = function(){
        return tcache.toArray(jive.model.isTask);
    }
    this.getTasks = day.getTasks;
    task_count_div.getTasks = that.getTasks;

    day.getCheckPoints = function(){
        return cpcache.toArray(jive.model.isCheckPoint);
    }
    this.getCheckPoints = day.getCheckPoints;
    cp_count_div.getCheckPoints = day.getCheckPoints;

    /**
	 * add a function to the day cell
	 * that lets it add events
	 *
	 * this will add the event into sorted position
	 * into this day cell
	 */
	day.appendEventDOM = function(txt){
		var tevent = txt.getEvent();
		for(var i=0;i<day.childNodes.length;i++){
			if($def(day.childNodes[i].getEvent)){
				if(day.childNodes[i].getEvent().getStart() > tevent.getStart()){
					day.insertBefore(txt, day.childNodes[i]);
					break;
				}
			}
		}
		if(i == day.childNodes.length){
			day.appendChild(txt);
		}
	};


    /**
	 * add a function to the day cell
	 * that lets it add events
	 *
	 * this will add the event into sorted position
	 * into this day cell
	 */
	day.appendCheckPointDOM = function(txt){
        // cache it.
        cpcache.put(txt.getCheckPoint().getID(), txt.getCheckPoint())

        jiveprojecttooltip = new JiveProjectTooltip(txt.getCheckPoint().getProject().getID(), "jive-note-checkpoint-body", "jive-note-tasks-body", "", "", "", "", "");
        jive.ext.x.xAddEventListener(cp_count_div, "mouseout", jiveprojecttooltip.cancelTooltip);

        cp_count++;
        jive.ext.x.xDisplayNone(txt);
        while(cp_count_div.childNodes.length > 0) cp_count_div.removeChild(cp_count_div.childNodes[0]);
        var s = (cp_count != 1) ? "s" : "";
        cp_count_div.appendChild(document.createTextNode(cp_count + " checkpoint" + s));
        jive.ext.x.xDisplayBlock(cp_count_div);
	};
    

    day.countVisibleItems = function(){
        return that.getTasks().length + that.getCheckPoints().length;
    }
    this.countVisibleItems = day.countVisibleItems;

    // listeners

	jive.ext.x.xAddEventListener(add_link, "click", function(aurora_gui, numDOM){ return function(evt){
		aurora_gui.notifyAddEventClicked(numDOM.getDate());
		jive.ext.x.xStopPropagation(evt);
	} }(aurora_gui, number), true);
	jive.ext.x.xAddEventListener(day, "click", function(aurora_gui, numDOM){ return function(){
		aurora_gui.notifyDayClicked(numDOM.getDate());
	} }(aurora_gui, number), false);

	jive.ext.x.xAddEventListener(day, "mouseover", day.mouseover);
	jive.ext.x.xAddEventListener(day, "mouseout", day.mouseout);

	// functions

	this.getDate = day.getDate;

	this.getDOM = function(){
		return day;
	}

}


/**
 * a month panel is composed of day cell holders, which in turn hold
 * the actual day cells. each holder holds 4 weeks of day cells. these
 * holders can be added to the beginning/end of the month view.
 *
 * to add the month panel to the UI, call it's getDOM() method
 * which returns a dom object
 */
jive.gui.BasicGui = function(control){

	/**
	 * selected item (event or task), or false if none is selected
	 */
	var selected_item = null;

	/**
	 * the list of listeners listening to this month view
	 */
	var listeners = new Array();

	/**
	 * the list of listeners listening to this month view's events
	 * they will recieve info about which events have been clicked etc.
	 */
	var event_listeners = new Array();

	this.addPriorityEventListener = function(list){
		event_listeners.splice(0,0,list);
	}
	this.addEventListener = function(list){
		event_listeners.push(list);
	}

	/**
	 * the current date of this month
	 */
	var currDate = new Date();
	currDate.setHours(17);

	/**
	 * the minimum date currently displayed on month view
	 */
	var currMin = new Date();
	currMin.setHours(17);
	/**
	 * the minimum date currently displayed on month view
	 */
	var currMax = new Date();
	currMax.setHours(17);

	/**
	 * this lets us reference this object from within anonymous objects
	 * inside this class
	 */
	var that = this;


    var has_init = false;
	this.hasInitHuh = function(){
		return has_init;
	}

	var veryinner = document.createElement('DIV');
	veryinner.className = "month_view_very_inner";

	var panel = document.createElement('DIV');
	panel.setAttribute("class", "month_view_main");
	panel.className = "month_view_main";

	var header = new jive.gui.SimpleHeader(control);
	
	var inner = document.createElement('DIV');
	inner.setAttribute("class", "month_view_inner");
	inner.className = "month_view_inner";



	var show_print = true;
	this.showPrintHuh = function(b){
		show_print = b;
		if(b && that.getActiveView().hasPrintView()){
			that.getHeader().showPrintHuh(true);
		}else{
			that.getHeader().showPrintHuh(false);
		}
	}
	this.shouldShowPrintHuh = function(){
		return show_print;
	}

	this.setNavFilter = function(foo){
		that.getHeader().setNavFilter(foo);
	}

	this.showFilterHuh = function(b){
		that.getHeader().showFilterHuh(b);
	}

    this.getFilterText = function(){
		return that.getHeader().getFilterText();
	}

	this.getHeaderFooterHeight = function(){
		return that.getHeader().getHeight();
	}


	/************************************************************************
	**
	** FUNCTIONS TO MANAGE MULTIPLE VIEWS
	**
	*************************************************************************/
	var LEFT_ACTION = function(){ };

	var RIGHT_ACTION = function(){ };

	function getLeftAction(){
		return LEFT_ACTION;
	}
	function getRightAction(){
		return RIGHT_ACTION;
	}

	var views = new jive.ext.y.HashTable();
	// returns true if an object is a view
	this.isViewHuh = function(v){
		return v != null && $obj(v) && $def(v.isExpandedHuh);
	}
	this.addView = function(view){
		var v = that.getView(view.getName());
		if(!$obj(v) || v == null){
			views.put(view.getHash(), view);
			//
			// if month view has already been init'd,
			// then we need to init the view right now.
			//
			// otherwise, the view will be init'd after
			// monthview's init'd
			//
			if(that.hasInitHuh()){
				view.init(veryinner);
				view.collapse();
			}
		}
	}
	this.removeView = function(name){
		views.clear(name);
	}
	this.getAllViews = function(){
		return views.toArray(that.isViewHuh);
	}
	this.getView = function(key){
		return views.get(key);
	}
	/**
	 * show the week view for this date
	 * d: the input to initialize teh view
	 * (it's a date for day/week/etc, adapter for list view, event for event view, etc)
	 * name: the name of the view to show
	 */
	this.showView = function(d, hash){
		var v = that.getAllViews();
		var has_view = false;
		for(var i=0;i<v.length;i++){
			if(v[i].getHash() == hash){
				has_view = true;
			}
		}
		if(!has_view) return that.showView(d, "month");

		for(var i=0;i<v.length;i++){
			if(v[i].getHash() == hash){
				if(!v[i].isExpandedHuh()){
					v[i].expand();
				}
				if(v[i].hasPrintView() && show_print){
					that.getHeader().showPrintHuh(true);
				}else{
					that.getHeader().showPrintHuh(false);
				}
				// show the view
				v[i].go(d);
				// update the header
				that.getHeader().setTitleText(v[i].getHeaderText(d).escapeHTML());
				// now listen for cliks to the next/prev buttons
				LEFT_ACTION = v[i].getPrevViewFunc();
				RIGHT_ACTION = v[i].getNextViewFunc();
				// reset the min/max times that are being shown
				currMin.setTime(v[i].getMinDate());
				currMax.setTime(v[i].getMaxDate());
				that.notifyTimesChanged(v[i].getMinDate(), v[i].getMaxDate());
			}else if(v[i].isExpandedHuh()){
				v[i].collapse();
			}
		}
		that.fixHeight();
	}

	/************************************************************************
	**
	** END FUNCTIONS TO MANAGE MULTIPLE VIEWS
	**
	*************************************************************************/

	/**
	 * return the current date of month view
	 */
	this.getCurrentDate = function(){
		var ret = new Date();
		ret.setTime(currDate.getTime());
		return ret;
	}
	/**
	 * sets the current date of month view
	 */
	this.setCurrentDate = function(d){
		currDate.setTime(d.getTime());
	}

	/**
	 * notify listeners that we're zooming to day view
	 */
	this.notifyPrintClicked = function(d){
		var foo = new Date();
		foo.setTime(d.getTime());
		for(var i=0;i<listeners.length;i++){
			listeners[i].printClicked(foo);
		}
	}


	/**
	 * hide the navigation arrows in the header
	 */
	this.hideArrows = function(){
		that.getHeader().showArrowsHuh(false);
	}

	/**
	 * show the navigation arrows in the header
	 */
	this.showArrows = function(){
		that.getHeader().showArrowsHuh(true);
	}

	/**
	 * make sure that our min date is set to
	 * at least dt
	 */
	this.ensureMinDate = function(dt){
		if(jive.model.dateLT(dt, currMin)){
			currMin.setTime(dt.getTime());
		}
	}
	/**
	 * make sure that our max date is set to
	 * at least dt
	 */
	this.ensureMaxDate = function(dt){
		if(jive.model.dateGT(dt, currMax)){
			currMax.setTime(dt.getTime());
		}
	}

	/**
	 * return the min date of month view
	 */
	this.getMinDate = function(){
		return currMin;
	}
	/**
	 * set the min date
	 */
	this.setMinDate = function(d){
		currMin.setTime(d.getTime());
	}
	/**
	 * return the min date of month view
	 */
	this.getMaxDate = function(){
		return currMax;
	}
	/**
	 * set the max date of month view
	 */
	this.setMaxDate = function(d){
		currMax.setTime(d.getTime());
	}

	/**
	 * the cell that will expand into month view
	 */
	var month_view = new jive.gui.MiniMonthView(control, that);


	this.updateText = function(){
		var v = that.getAllViews();
		for(var i=0;i<v.length;i++){
			v[i].updateText();
		}

		var tlang = control.getLanguageManager().getActiveLanguage();
		that.getHeader().updateText()
		that.getHeader().setTitleText(that.getActiveView().getHeaderText(that.getCurrentDate()).escapeHTML());
	}

	/**
	 * now tie all the dom objects together
	 */
	panel.appendChild(header.getDOM());
	panel.appendChild(inner);
	inner.appendChild(veryinner);



	/**
	 * return the DOM object that represents this month view
	 */
	this.getDOM = function(){
		return panel;
	}


	/**
	 * gets the events for a specific day
	 * @param dt a date object
	 */
	this.getEventsOn = function(dt){
		return that.getView("month").getEventsOn(dt);
	}

	/**
	 * gets the tasks for a specific day
	 * @param dt a Date object
	 */
	this.getTasksOn = function(dt){
		return that.getView("month").getTasksOn(dt);
	}


	/**
	 * add (or refresh) an event to display on this month view
	 * i need to update this function. its not handling teh drag listeners
	 * correctly. i need to remove old listeners before i add new ones...
	 */
	this.addEvent = function(tevent){
		var v = that.getAllViews();
		for(var i=0;i<v.length;i++){
			v[i].addEvent(tevent);
		}
	}


	/**
	 * add a task to display on this month view
	 */
	this.addTask = function(ttask){
		var v = that.getAllViews();
		for(var i=0;i<v.length;i++){
			v[i].addTask(ttask);
		}
	}

	/**
	 * removes an event from the month view
	 *
	 * this removes any drag listeners from span elements
	 * for that event, and disables the drag
	 */
	this.removeEvent = function(tevent){
		var v = that.getAllViews();
		for(var i=0;i<v.length;i++){
			v[i].removeEvent(tevent);
		}
	}

	/**
	 * removes an event from the month view
	 *
	 * this removes any drag listeners from span elements
	 * for that event, and disables the drag
	 */
	this.removeTask = function(ttask){
		var v = that.getAllViews();
		for(var i=0;i<v.length;i++){
			v[i].removeTask(ttask);
		}
	}

	/**
	 * this flushes a calendar entirely. it removes it from teh views,
	 * and also removes any DOM's we have cached for this calendar's
	 * events
	 */
	this.flushCalendar = function(cal){
		var v = that.getAllViews();
		for(var i=0;i<v.length;i++){
			v[i].flushCalendar(cal);
		}
	}

	/**
	 * this flushes an event entirely. it removes it from the views,
	 * and also removes any DOM's we have cached for that event
	 */
	this.flushEvent = function(tevent){
		var v = that.getAllViews();
		for(var i=0;i<v.length;i++){
			v[i].flushEvent(tevent);
		}

		if(jotlet.model.isEvent(selected_item) && selected_item.getId() == tevent.getId()){
			selected_item = null;
		}
	}

	/**
	 * this flushes a task entirely. it removes it from the views,
	 * and also removes any DOM's we have cached for that task
	 */
	this.flushTask = function(ttask){
		var v = that.getAllViews();
		for(var i=0;i<v.length;i++){
			v[i].flushTask(ttask);
		}

		if(jotlet.model.isTask(selected_item) && selected_item.getId() == ttask.getId()){
			selected_item = null;
		}
	}


	this.refreshWeather = function(){
		var v = that.getAllViews();
		for(var i=0;i<v.length;i++){
			if($def(v[i].refreshWeather)){
				v[i].refreshWeather();
			}
		}
	}

	/**
	 * shows the month view for
	 * Date d
	 * @param d the date of the month to show
	 */
	this.refreshShading = function(){
		var v = that.getAllViews();
		for(var i=0;i<v.length;i++){
			if($def(v[i].refreshShading)){
				v[i].refreshShading();
			}
		}
	}

	this.getActiveView = function(){
		var v = that.getAllViews();
		for(var i=0;i<v.length;i++){
			if(v[i].isExpandedHuh()){
				return v[i];
			}
		}
		return that.getView("month");
	}


	/**
	 * show the month view for this date
	 */
	this.showMonth = function(dtemp){
		that.showView(dtemp, "month");
	}

	/**
	 * show the week view for this date
	 */
	this.showWeek = function(dtemp){
		that.showView(dtemp, "week");
	}

	/**
	 * show the day view for this date
	 */
	this.showDay = function(dtemp){
		that.showView(dtemp, "day");
	}

	/**
	 * show the list view for this date
	 */
	this.showList = function(dtemp){
		that.showView(dtemp, "list");
	}

	/**
	 * refresh the text on the bar, possibly because
	 * of a settings change, etc
	 */
	this.refresh = function(){
		var v = that.getAllViews();
		for(var i=0;i<v.length;i++){
			if(v[i].isExpandedHuh()){
				v[i].refresh();
				that.refreshShading();
				return;
			}
		}
		that.showMonth(that.getCurrentDate());
		that.refreshShading();
	}


	this.fixHeight = function(){
		try{
			if(jive.ext.x.xParent(panel) != null){
				var inner_height = jive.ext.x.xHeight(jive.ext.x.xParent(panel)) - that.getHeaderFooterHeight();
				jive.ext.x.xHeight(inner, inner_height);
				that.getView("month").fixHeight(inner_height);
			}
		}catch(e){
			alert(e);
		}
	}

	/**
	 * we've just now been placed in our parent div, so
	 * adjust height of our holders etc to fit
	 */
	this.init = function(){
		has_init = true;
		var v = that.getAllViews();
		for(var i=0;i<v.length;i++){
			v[i].init(veryinner);
		}
	}

	this.isShowingDay = function(){
		return that.getView("day").isExpandedHuh();
	}

	this.isShowingWeek = function(){
		return that.getView("week").isExpandedHuh();
	}


	this.killYourself = function(){
		control = null;
		for(var i=0;i<views.length;i++){
			views[i].killYourself();
		}
	}


	/******************************************
	 * add default views
	 ******************************************/
	that.addView(month_view);


	/******************************************
	 * listener functions
	 ******************************************/
	this.addListener = function(list){
		listeners.push(list);
	}


	/**
	 * notify everybody else that a drag/drop happened
	 */
	this.notifyStopDrag = function(tevent, dt, left, tp){
		var doneHuh = false;
		var v = that.getAllViews();
		for(var i=0;i<v.length;i++){
			if(v[i].isExpandedHuh() && $def(v[i].stopDrag)){
				doneHuh = v[i].stopDrag(tevent, dt, left, tp);
			}
		}
		if(!doneHuh){
			for(var i=0;i<listeners.length && !doneHuh;i++){
				doneHuh = listeners[i].stopDrag(tevent, dt, left, tp);
			}
		}
		return doneHuh;
	}

	// this is the last day cell that
	// we've hovered over when dragging
	// an event or task
	var hovered_day_cell = null;
	var threadNum = 0;
	this.notifyDragging = function(tevent, dt, left, tp){
		// this function will be called as they drag around an event
		// which means it'll be called probably as its running
		//
		// the threadNum and myThread variables will make sure that
		// I drop out of execution asap as a new thread starts.
		threadNum++;
		var myThread = threadNum;

		// track if we show a drop zone or not
		var zonedHuh = false;

		var v = that.getAllViews();
		for(var i=0;i<v.length;i++){
			if(v[i].isExpandedHuh()){
				if($def(v[i].dragging)){
					zonedHuh = v[i].dragging(tevent, dt, left, tp);
				}
			}
		}
		if(!zonedHuh){
			for(var i=0;i<listeners.length && !zonedHuh;i++){
				zonedHuh = zonedHuh || listeners[i].dragging(tevent, dt, left, tp);
			}
			if(!zonedHuh){
				control.hideHover();
			}
		}
		return zonedHuh;
	}

	/**
	 * notify listeners that we're zooming to day view
	 */
	this.notifyDayClicked = function(d){
		var foo = new Date();
		foo.setTime(d.getTime());
		for(var i=0;i<listeners.length;i++){
			listeners[i].dayClicked(foo);
		}
	}

	/**
	 * notify listeners that the min/max date for month view
	 * has changed
	 */
	this.notifyTimesChanged = function(mind, maxd){
		for(var i=0;i<listeners.length;i++){
			listeners[i].timesChanged(mind, maxd);
		}
	}


	/**
	 * the user has quick added a task to the calendar/date
	 */
	this.notifyQuickAddTask = function(title, calendar, date){
		for(var i=0;i<listeners.length;i++){
			listeners[i].quickAddTask(title, calendar, date);
		}
	}


	this.filter = function(str){
		var v = that.getAllViews();
		for(var i=0;i<v.length;i++){
			if(v[i].isExpandedHuh()){
				if($def(v[i].filter)){
					v[i].filter(str);
				}
			}
		}
	}

	/**
	 * toggle event visibility if
	 * the calendar visibility is switched (ie, in the sidebar)
	 */
	this.calendarVisible = function(calendar, visibleHuh){
		var v = that.getAllViews();
		for(var i=0;i<v.length;i++){
			if($def(v[i].calendarVisible)){
				v[i].calendarVisible(calendar, visibleHuh);
			}
		}
		that.refreshShading();
	}

	/******************************************
	 * end listener functions
	 * begin event listener functions
	 ******************************************/


	/**
	 * notify listeners that the user clicked an event
	 */
	this.notifyEventClicked = function(tevent){
		for(var i=0;i<event_listeners.length;i++){
			event_listeners[i].eventClicked(tevent);
		}
	}
    /**
     * notify listeners that the user clicked a task
     */
    this.notifyTaskClicked = function(ttask){
        try{
            for(var i=0;i<event_listeners.length;i++){
                event_listeners[i].taskClicked(ttask);
            }
        }catch(e){
            alert(e);
        }
    }
    /**
     * notify listeners that the user clicked a checkpoint
     */
    this.notifyCheckPointClicked = function(cp){
        try{
            for(var i=0;i<event_listeners.length;i++){
                event_listeners[i].checkPointClicked(cp);
            }
        }catch(e){
            alert(e);
        }
    }

	/**
	 * notify listeners that the user double clicked an event
	 */
	this.notifyEventDblClicked = function(tevent){
		for(var i=0;i<event_listeners.length;i++){
			event_listeners[i].eventDblClicked(tevent);
		}
	}

    /**
     * notify listeners that the user double clicked a task
     */
    this.notifyTaskDblClicked = function(ttask){
        for(var i=0;i<event_listeners.length;i++){
            event_listeners[i].taskDblClicked(ttask);
        }
    }

    /**
     * notify listeners that the user double clicked a task
     */
    this.notifyCheckPointDblClicked = function(cp){
        for(var i=0;i<event_listeners.length;i++){
            event_listeners[i].checkPointDblClicked(cp);
        }
    }

	/**
	 * notify listeners that we want to unselect all the events
	 */
	this.notifyUnselectAll = function(){
		for(var i=0;i<event_listeners.length;i++){
			event_listeners[i].unselectAll();
		}
	}
	/******************************************
	 * end event listener functions
	 ******************************************/

	/******************************************
	 * navlistener functions
	 ******************************************/
	var nav_listeners = new Array();
	this.addNavListener = function(list){
		nav_listeners.push(list);
	}

	this.removeNavListener = function(list){
		for(var i=0;i<nav_listeners.length;i++){
			if(nav_listeners[i] == list){
				nav_listeners.splice(i, 1);
			}
		}
	}

	this.notifyMonthClicked = function(d){
		for(var i=0;i<nav_listeners.length;i++){
			nav_listeners[i].monthClicked(d);
		}
	}

	this.notifyWeekClicked = function(d){
		for(var i=0;i<nav_listeners.length;i++){
			nav_listeners[i].weekClicked(d);
		}
	}

	this.notifyDayClicked = function(d){
		for(var i=0;i<nav_listeners.length;i++){
			nav_listeners[i].dayClicked(d);
		}
	}

	this.notifyListClicked = function(){
		for(var i=0;i<nav_listeners.length;i++){
			nav_listeners[i].listClicked();
		}
	}

	this.notifyAddEventClicked = function(d){
		for(var i=0;i<nav_listeners.length;i++){
			nav_listeners[i].addEventClicked(d);
		}
	}

	this.notifyBackClicked = function(){
		for(var i=0;i<nav_listeners.length;i++){
			nav_listeners[i].backClicked();
		}
	}
	/******************************************
	 * end nav listener functions
	 ******************************************/

	/**
	 * add a listener to listen for event clicks
	 * when an event is clicked, highlight it in
	 * the main view, and unhighlight (if needed)
	 * the previously highlighted event
	 */
	var list = new Object();
	list.eventClicked = function(tevent){
		selected_item = tevent;
	}
	list.eventDblClicked = function(tevent){ }
    list.taskClicked = function(ttask){
        selected_item = ttask;
    }
    list.taskDblClicked = function(ttask){ }
    list.checkPointClicked = function(cp){
        selected_item = cp;
    }
    list.checkPointDblClicked = function(cp){ }
	list.unselectAll = function(){
		selected_item = null;
	}
	this.addEventListener(list);

	/**
	 * unselects teh current event in the view
	 */
	this.unselectAll = function(){
		that.notifyUnselectAll();
	}


	/**
	 * returns teh currently selected event,
	 * or null if none is selected
	 */
	this.getSelectedItem = function(){
		return selected_item;
	}



	//
	// listen to the header
	//
	var head_list = new Object();
	head_list.printClicked = function(thunk, date_thunk){
		return function(){
			thunk(date_thunk());
		}
	}(that.notifyPrintClicked, that.getCurrentDate);
	head_list.leftClicked = function(foo){
		return function(){
			var func = foo();
			func();
		}
	}(getLeftAction);
	head_list.rightClicked = function(foo){ return function(){ var func = foo(); func(); }}(getRightAction);
	head_list.searchByText = function(str){
		var v = that.getAllViews();
		for(var i=0;i<v.length;i++){
			if($def(v[i].filter)){
				v[i].filter(str);
			}
		}
	}
	header.addListener(head_list);




	this.setHeader = function(h){
		panel.removeChild(header.getDOM());
		header.killYourself();
		header = h;
		header.addListener(head_list);
		panel.insertBefore(header.getDOM(), inner);
	}

	this.getHeader = function(){
		return header;
	}

}



/**
 * the header for jotlet
 */
jive.gui.SimpleHeader = function(control, par){

	var that;
	if($obj(par)){
		that = par;
	}else{
		that = this;
	}

	var button_wrap = document.createElement('DIV');
	button_wrap.setAttribute("class", "month_view_header_button_wrap");
	button_wrap.className = "month_view_header_button_wrap";

	var header = document.createElement('DIV');
	header.setAttribute("class", "month_view_header color_header");
	header.className = "month_view_header color_header";
	header.appendChild(document.createElement('DIV'));
	var l_arrow = document.createElement("span");
	l_arrow.setAttribute("class", "month_view_header_link");
	l_arrow.className = "month_view_header_link";
	button_wrap.appendChild(l_arrow);
	l_arrow.appendChild(document.createTextNode("<"));
	var r_arrow = document.createElement("span");
	r_arrow.setAttribute("class", "month_view_float_r month_view_header_link");
	r_arrow.className = "month_view_float_r month_view_header_link";
	r_arrow.appendChild(document.createTextNode(">"));
	button_wrap.appendChild(r_arrow);
	var monthName = document.createElement('SPAN');
	monthName.setAttribute("class", "month_view_headerc");
	monthName.className = "month_view_headerc";
	button_wrap.appendChild(monthName);
	header.appendChild(button_wrap);

	jive.ext.x.xDisplayBlock(header);

	this.getDOM = function(){
		return header;
	}

	this.showArrowsHuh = function(b){
		if(b){
			jive.ext.x.xDisplayBlock(l_arrow);
			jive.ext.x.xShow(r_arrow);
		}else{
			jive.ext.x.xDisplayNone(l_arrow);
			jive.ext.x.xHide(r_arrow);
		}
	}

	this.showPrintHuh = function(b){ }

	this.showFilterHuh = function(b){ }

	this.getHeight = function(){
		return (jive.ext.x.xDisplay(header) == "block") ? jive.ext.x.xHeight(header) : 0;
	}

	this.setTitleText = function(str){
		while(monthName.childNodes.length > 0) monthName.removeChild(monthName.childNodes[0]);
		monthName.appendChild(document.createTextNode(str));
	}


	this.setNavFilter = function(foo){ }

	this.updateText = function(){ }

	this.getFilterText = function(){
		return "";
	}



	/****************************************
	 **
	 ** listeners
	 **
	 ****************************************/

	var listeners = new Array();

	this.addListener = function(list){
		listeners.push(list);
	}

	/**
	 * notify the listeners that the print button
	 * was clicked
	 */
	this.notifyPrintClicked = function(){
		for(var i=0;i<listeners.length;i++){
			listeners[i].printClicked();
		}
	}

	/**
	 * the left arrow was clicked
	 */
	this.notifyLeftClicked = function(){
		for(var i=0;i<listeners.length;i++){
			listeners[i].leftClicked();
		}
	}

	/**
	 * the right arrow was clicked
	 */
	this.notifyRightClicked = function(){
		for(var i=0;i<listeners.length;i++){
			listeners[i].rightClicked();
		}
	}

	/**
	 * the ui should filter by text
	 */
	this.notifySearchByText = function(str){
		for(var i=0;i<listeners.length;i++){
			listeners[i].searchByText(str);
		}
	}



	/****************************************
	 **
	 ** events
	 **
	 ****************************************/
	jive.ext.x.xAddEventListener(l_arrow, "click", function(that){ return function(){ that.notifyLeftClicked(); } }(that));
	jive.ext.x.xAddEventListener(r_arrow, "click", function(that){ return function(){ that.notifyRightClicked(); } }(that));


	/****************************************
	 **
	 ** destructor
	 **
	 ****************************************/

	this.killYourself = function(){
	}
}




/**
 * the header for jotlet
 */
jive.gui.NullHeader = function(control){

    var that = this;


	var header = document.createElement('DIV');
	jive.ext.x.xDisplayNone(header);

	this.getDOM = function(){
		return header;
	}

	this.showArrowsHuh = function(b){ }

	this.showPrintHuh = function(b){ }

	this.showFilterHuh = function(b){ }

	this.getHeight = function(){
		return 0;
	}

	this.setTitleText = function(str){ }

	this.setNavFilter = function(foo){ }

	this.updateText = function(){ }

	this.getFilterText = function(){
		return "";
	}

	/****************************************
	 **
	 ** listeners
	 **
	 ****************************************/

	var listeners = new Array();

	this.addListener = function(list){
		listeners.push(list);
	}

	/**
	 * notify the listeners that the print button
	 * was clicked
	 */
	this.notifyPrintClicked = function(){
		for(var i=0;i<listeners.length;i++){
			listeners[i].printClicked();
		}
	}

	/**
	 * the left arrow was clicked
	 */
	this.notifyLeftClicked = function(){
		for(var i=0;i<listeners.length;i++){
			listeners[i].leftClicked();
		}
	}

	/**
	 * the right arrow was clicked
	 */
	this.notifyRightClicked = function(){
		for(var i=0;i<listeners.length;i++){
			listeners[i].rightClicked();
		}
	}

	/**
	 * the ui should filter by text
	 */
	this.notifySearchByText = function(str){
		for(var i=0;i<listeners.length;i++){
			listeners[i].searchByText(str);
		}
	}

	/****************************************
	 **
	 ** destructor
	 **
	 ****************************************/

	this.killYourself = function(){
	}
}




var pc = navigator.userAgent.toLowerCase();
var ie4_win = (pc.indexOf("win")!=-1) && (pc.indexOf("msie") != -1)
    && (parseInt(navigator.appVersion) >= 4);

// only builds based upon gecko later than Jan 8th support the selectionStart, selectionEnd properly
var is_gecko = pc.indexOf("gecko/") != -1 &&
    parseFloat(pc.substring(pc.indexOf("gecko/") + 6, pc.indexOf("gecko/") + 14)) > 20030108;

function styleTag(tag, endtag, ta) {
    var end = 0;
    var scrollTop = ta.scrollTop;
    var r;
    if (document.selection) {
        if (document.selection.createRange().parentElement().tagName == 'TEXTAREA') {
            var selected = document.selection.createRange().text;

            // now determine the cursor position
            end = getSelectionRangeEnd(ta);

            // r is an array
            r = _markupText(selected, tag, endtag);
            // update text
            document.selection.createRange().text = r[0];
            // update end position
            end += r[1];
        }
    }
    else if (typeof(ta.selectionStart) != 'undefined' && typeof(ta.selectionEnd) != 'undefined') {
        if (ta.selectionStart == ta.selectionEnd) {
            return;
        }
        var selLength = ta.textLength;
        var selStart = ta.selectionStart;
        var selEnd = ta.selectionEnd;
        if (selEnd == 1 || selEnd == 2) {
            selEnd = selLength;
        }
        var s1 = (ta.value).substring(0, selStart);
        var s2 = (ta.value).substring(selStart, selEnd);
        var s3 = (ta.value).substring(selEnd, selLength);

        // r is an array
        r = _markupText(s2, tag, endtag);
        // update text
        ta.value = s1 + r[0] + s3;
        // update end position
        end = selEnd + r[1];
    }
    else {
        return;
    }

    if (end > 0) {
        setCaretTo(ta, end, scrollTop);
    }
}

function _markupText(text, tag, endtag) {
    // trim off leading whitespace from selection (includes newlines)
    var r = trimLeadingSpace(text);
    text = r[0];
    var removeSpace = r[1];

    // trim off trailing whitespace from selection (includes newlines)
    r = trimTrailingSpace(text);
    text = r[0];
    var addSpace = r[1];

    // determine if selection crosses multiple lines
    var addedCharacters = 0;

    if (text.indexOf('\n') > 0) {
        // wrap whole lines in the tag
        var lines = text.split('\n');
        var newText = '';
        for (var i = 0; i < lines.length; i++) {
            var line = lines[i];

            r = trimLeadingSpace(line);
            line = r[0];
            var leadingSpaces = r[1];

            // trim off trailing whitespace from selection (includes newlines)
            r = trimTrailingSpace(line);
            line = r[0];
            var trailingSpaces = r[1];

            if (line == '') {
                newText += leadingSpaces + line + trailingSpaces;
            }
            else {
                newText += leadingSpaces + tag + line + endtag + trailingSpaces;
                addedCharacters += (tag.length + endtag.length);
            }

            if (i < lines.length -1) {
                newText += '\n';
            }
        }
        text = removeSpace + newText + addSpace;
    }
    else {
        text = removeSpace + tag + text + endtag + addSpace;
    }

    r = new Array(2);
    r[0] = text;
    r[1] = addedCharacters;

    return r;
}

function trimLeadingSpace(text) {
    var removeSpace = "";
    while (text.length > 0 && 
           (text.charAt(0) == ' ' || 
            text.charAt(0) == '\n' ||
            text.charAt(0) == '\r')) 
    {
        removeSpace += text.charAt(0);
        text = text.substring(1);
    }
    
    var r = new Array(2);
    r[0] = text;
    r[1] = removeSpace;
    return r;
}

function trimTrailingSpace(text) {
    var addSpace = "";
    while (text.length > 0 && 
           (text.charAt(text.length-1) == ' ' || 
            text.charAt(text.length-1) == '\n' || 
           text.charAt(text.length-1) == '\r')) 
    {
        addSpace += text.charAt(text.length-1);        
        text = text.substring(0, text.length-1);
    }
    
    var r = new Array(2);
    r[0] = text;
    r[1] = addSpace;
    return r;
}

function getSelectionRangeText(ta) {
    if (document.selection) {
        // The current selection
        return document.selection.createRange().text;
    }
    else if (is_gecko) {
        var start = ta.selectionStart;
        var end   = ta.selectionEnd;
        return ta.value.substr(start, end-start);
    }
    else {
        return '';
    }
}

function getSelectionRangeEnd(ta) {
    if (document.selection) { 
        // The current selection 
        var range = document.selection.createRange(); 
        // We'll use this as a 'dummy' 
        var stored_range = range.duplicate(); 
        // Select all text 
        stored_range.moveToElementText(ta); 
        // Now move 'dummy' end point to end point of original range 
        stored_range.setEndPoint('EndToEnd', range); 
        
        // Now we can calculate start and end points
        var start = stored_range.text.length - range.text.length; 
        return start + range.text.length;
    }
    else if (is_gecko) {
        return ta.selectionEnd;
    }
    else {
        return 0;
    }
}
function setCaretTo(ta, pos, scrollTop) { 
    ta.focus();
    if (ta.createTextRange) {         
        // we need to determine the number of \r\n in IE because while they are two separate
        // characters they are treated as one for the purposes of position
        var i = 0;
        var count = 0;
        var text = ta.value;
        while (i > -1 && i < pos) {
            i = text.indexOf("\r\n", i);
            if (i >= 0) {
                count++;
                i += 2;
            }
        }
        // knock one off of count 
        if (count > 1) {
            count--;
        }
        
        // position cursor
        var range = ta.createTextRange();        
        range.move("character", (pos - count)); 
        range.select(); 
    } 
    else if (ta.selectionStart) {
        ta.setSelectionRange(pos, pos);        
    }
    
    // scroll to the same location that the textarea was previously scrolled to
    if (scrollTop > 0) {
        ta.scrollTop = scrollTop;
    }
}

function caret(ta) {
    if (ie4_win && ta.createTextRange &&
            document.selection.createRange().parentElement().tagName == 'TEXTAREA')
    {
        ta.caretPos = document.selection.createRange().duplicate();
    }
}

// This function returns the name of a given function. It does this by
// converting the function to a string, then using a regular expression
// to extract the function name from the resulting code.
function funcname(f) {
    var s = f.toString().match(/function (\w*)/)[1];
    if ((s == null) || (s.length == 0)) return "anonymous";
    return s;
}

// This function returns a string that contains a "stack trace."
function stacktrace() {
    var s = "";  // This is the string we'll return.
    // Loop through the stack of functions, using the caller property of
    // one arguments object to refer to the next arguments object on the
    // stack.
    for(var a = arguments.caller; a != null; a = a.caller) {
        // Add the name of the current function to the return value.
        s += funcname(a.callee) + "\n";

        // Because of a bug in Navigator 4.0, we need this line to break.
        // a.caller will equal a rather than null when we reach the end
        // of the stack. The following line works around this.
        if (a.caller == a) break;
    }
    return s;
}

function printStackTrace() {
    alert('stack trace is ' + stacktrace());
}


/*  Prototype JavaScript framework, version 1.6.0.2
 *  (c) 2005-2008 Sam Stephenson
 *
 *  Prototype is freely distributable under the terms of an MIT-style license.
 *  For details, see the Prototype web site: http://www.prototypejs.org/
 *
 *--------------------------------------------------------------------------*/

/*
 * JIVE SOFTWARE ENGINEERS
 *
 * please note the following changes to this document:
 * line 3929 -> added conditional checks for element.stopObserving
 *              see svn revision 66086
 */

var Prototype = {
  Version: '1.6.0.2',

  Browser: {
    IE:     !!(window.attachEvent && !window.opera),
    Opera:  !!window.opera,
    WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
    Gecko:  navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1,
    MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
  },

  BrowserFeatures: {
    XPath: !!document.evaluate,
    ElementExtensions: !!window.HTMLElement,
    SpecificElementExtensions:
      document.createElement('div').__proto__ &&
      document.createElement('div').__proto__ !==
        document.createElement('form').__proto__
  },

  ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
  JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,

  emptyFunction: function() { },
  K: function(x) { return x }
};

if (Prototype.Browser.MobileSafari)
  Prototype.BrowserFeatures.SpecificElementExtensions = false;


/* Based on Alex Arnell's inheritance implementation. */
var Class = {
  create: function() {
    var parent = null, properties = $A(arguments);
    if (Object.isFunction(properties[0]))
      parent = properties.shift();

    function klass() {
      this.initialize.apply(this, arguments);
    }

    Object.extend(klass, Class.Methods);
    klass.superclass = parent;
    klass.subclasses = [];

    if (parent) {
      var subclass = function() { };
      subclass.prototype = parent.prototype;
      klass.prototype = new subclass;
      parent.subclasses.push(klass);
    }

    for (var i = 0; i < properties.length; i++)
      klass.addMethods(properties[i]);

    if (!klass.prototype.initialize)
      klass.prototype.initialize = Prototype.emptyFunction;

    klass.prototype.constructor = klass;

    return klass;
  }
};

Class.Methods = {
  addMethods: function(source) {
    var ancestor   = this.superclass && this.superclass.prototype;
    var properties = Object.keys(source);

    if (!Object.keys({ toString: true }).length)
      properties.push("toString", "valueOf");

    for (var i = 0, length = properties.length; i < length; i++) {
      var property = properties[i], value = source[property];
      if (ancestor && Object.isFunction(value) &&
          value.argumentNames().first() == "$super") {
        var method = value, value = Object.extend((function(m) {
          return function() { return ancestor[m].apply(this, arguments) };
        })(property).wrap(method), {
          valueOf:  function() { return method },
          toString: function() { return method.toString() }
        });
      }
      this.prototype[property] = value;
    }

    return this;
  }
};

var Abstract = { };

Object.extend = function(destination, source) {
  for (var property in source)
    destination[property] = source[property];
  return destination;
};

Object.extend(Object, {
  inspect: function(object) {
    try {
      if (Object.isUndefined(object)) return 'undefined';
      if (object === null) return 'null';
      return object.inspect ? object.inspect() : String(object);
    } catch (e) {
      if (e instanceof RangeError) return '...';
      throw e;
    }
  },

  toJSON: function(object) {
    var type = typeof object;
    switch (type) {
      case 'undefined':
      case 'function':
      case 'unknown': return;
      case 'boolean': return object.toString();
    }

    if (object === null) return 'null';
    if (object.toJSON) return object.toJSON();
    if (Object.isElement(object)) return;

    var results = [];
    for (var property in object) {
      var value = Object.toJSON(object[property]);
      if (!Object.isUndefined(value))
        results.push(property.toJSON() + ': ' + value);
    }

    return '{' + results.join(', ') + '}';
  },

  toQueryString: function(object) {
    return $H(object).toQueryString();
  },

  toHTML: function(object) {
    return object && object.toHTML ? object.toHTML() : String.interpret(object);
  },

  keys: function(object) {
    var keys = [];
    for (var property in object)
      keys.push(property);
    return keys;
  },

  values: function(object) {
    var values = [];
    for (var property in object)
      values.push(object[property]);
    return values;
  },

  clone: function(object) {
    return Object.extend({ }, object);
  },

  isElement: function(object) {
    return object && object.nodeType == 1;
  },

  isArray: function(object) {
    return object != null && typeof object == "object" &&
      'splice' in object && 'join' in object;
  },

  isHash: function(object) {
    return object instanceof Hash;
  },

  isFunction: function(object) {
    return typeof object == "function";
  },

  isString: function(object) {
    return typeof object == "string";
  },

  isNumber: function(object) {
    return typeof object == "number";
  },

  isUndefined: function(object) {
    return typeof object == "undefined";
  }
});

Object.extend(Function.prototype, {
  argumentNames: function() {
    var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip");
    return names.length == 1 && !names[0] ? [] : names;
  },

  bind: function() {
    if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
    var __method = this, args = $A(arguments), object = args.shift();
    return function() {
      return __method.apply(object, args.concat($A(arguments)));
    }
  },

  bindAsEventListener: function() {
    var __method = this, args = $A(arguments), object = args.shift();
    return function(event) {
      return __method.apply(object, [event || window.event].concat(args));
    }
  },

  curry: function() {
    if (!arguments.length) return this;
    var __method = this, args = $A(arguments);
    return function() {
      return __method.apply(this, args.concat($A(arguments)));
    }
  },

  delay: function() {
    var __method = this, args = $A(arguments), timeout = args.shift() * 1000;
    return window.setTimeout(function() {
      return __method.apply(__method, args);
    }, timeout);
  },

  wrap: function(wrapper) {
    var __method = this;
    return function() {
      return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));
    }
  },

  methodize: function() {
    if (this._methodized) return this._methodized;
    var __method = this;
    return this._methodized = function() {
      return __method.apply(null, [this].concat($A(arguments)));
    };
  }
});

Function.prototype.defer = Function.prototype.delay.curry(0.01);

Date.prototype.toJSON = function() {
  return '"' + this.getUTCFullYear() + '-' +
    (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
    this.getUTCDate().toPaddedString(2) + 'T' +
    this.getUTCHours().toPaddedString(2) + ':' +
    this.getUTCMinutes().toPaddedString(2) + ':' +
    this.getUTCSeconds().toPaddedString(2) + 'Z"';
};

var Try = {
  these: function() {
    var returnValue;

    for (var i = 0, length = arguments.length; i < length; i++) {
      var lambda = arguments[i];
      try {
        returnValue = lambda();
        break;
      } catch (e) { }
    }

    return returnValue;
  }
};

RegExp.prototype.match = RegExp.prototype.test;

RegExp.escape = function(str) {
  return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
};

/*--------------------------------------------------------------------------*/

var PeriodicalExecuter = Class.create({
  initialize: function(callback, frequency) {
    this.callback = callback;
    this.frequency = frequency;
    this.currentlyExecuting = false;

    this.registerCallback();
  },

  registerCallback: function() {
    this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
  },

  execute: function() {
    this.callback(this);
  },

  stop: function() {
    if (!this.timer) return;
    clearInterval(this.timer);
    this.timer = null;
  },

  onTimerEvent: function() {
    if (!this.currentlyExecuting) {
      try {
        this.currentlyExecuting = true;
        this.execute();
      } finally {
        this.currentlyExecuting = false;
      }
    }
  }
});
Object.extend(String, {
  interpret: function(value) {
    return value == null ? '' : String(value);
  },
  specialChar: {
    '\b': '\\b',
    '\t': '\\t',
    '\n': '\\n',
    '\f': '\\f',
    '\r': '\\r',
    '\\': '\\\\'
  }
});

Object.extend(String.prototype, {
  gsub: function(pattern, replacement) {
    var result = '', source = this, match;
    replacement = arguments.callee.prepareReplacement(replacement);

    while (source.length > 0) {
      if (match = source.match(pattern)) {
        result += source.slice(0, match.index);
        result += String.interpret(replacement(match));
        source  = source.slice(match.index + match[0].length);
      } else {
        result += source, source = '';
      }
    }
    return result;
  },

  sub: function(pattern, replacement, count) {
    replacement = this.gsub.prepareReplacement(replacement);
    count = Object.isUndefined(count) ? 1 : count;

    return this.gsub(pattern, function(match) {
      if (--count < 0) return match[0];
      return replacement(match);
    });
  },

  scan: function(pattern, iterator) {
    this.gsub(pattern, iterator);
    return String(this);
  },

  truncate: function(length, truncation) {
    length = length || 30;
    truncation = Object.isUndefined(truncation) ? '...' : truncation;
    return this.length > length ?
      this.slice(0, length - truncation.length) + truncation : String(this);
  },

  strip: function() {
    return this.replace(/^\s+/, '').replace(/\s+$/, '');
  },

  stripTags: function() {
    return this.replace(/<\/?[^>]+>/gi, '');
  },

  stripScripts: function() {
    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
  },

  extractScripts: function() {
    var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
    var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
    return (this.match(matchAll) || []).map(function(scriptTag) {
      return (scriptTag.match(matchOne) || ['', ''])[1];
    });
  },

  evalScripts: function() {
    return this.extractScripts().map(function(script) { return eval(script) });
  },

  escapeHTML: function() {
    var self = arguments.callee;
    self.text.data = this;
    return self.div.innerHTML;
  },

  unescapeHTML: function() {
    var div = new Element('div');
    div.innerHTML = this.stripTags();
    return div.childNodes[0] ? (div.childNodes.length > 1 ?
      $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :
      div.childNodes[0].nodeValue) : '';
  },

  toQueryParams: function(separator) {
    var match = this.strip().match(/([^?#]*)(#.*)?$/);
    if (!match) return { };

    return match[1].split(separator || '&').inject({ }, function(hash, pair) {
      if ((pair = pair.split('='))[0]) {
        var key = decodeURIComponent(pair.shift());
        var value = pair.length > 1 ? pair.join('=') : pair[0];
        if (value != undefined) value = decodeURIComponent(value);

        if (key in hash) {
          if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
          hash[key].push(value);
        }
        else hash[key] = value;
      }
      return hash;
    });
  },

  toArray: function() {
    return this.split('');
  },

  succ: function() {
    return this.slice(0, this.length - 1) +
      String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
  },

  times: function(count) {
    return count < 1 ? '' : new Array(count + 1).join(this);
  },

  camelize: function() {
    var parts = this.split('-'), len = parts.length;
    if (len == 1) return parts[0];

    var camelized = this.charAt(0) == '-'
      ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
      : parts[0];

    for (var i = 1; i < len; i++)
      camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);

    return camelized;
  },

  capitalize: function() {
    return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
  },

  underscore: function() {
    return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
  },

  dasherize: function() {
    return this.gsub(/_/,'-');
  },

  inspect: function(useDoubleQuotes) {
    var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
      var character = String.specialChar[match[0]];
      return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
    });
    if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
    return "'" + escapedString.replace(/'/g, '\\\'') + "'";
  },

  toJSON: function() {
    return this.inspect(true);
  },

  unfilterJSON: function(filter) {
    return this.sub(filter || Prototype.JSONFilter, '#{1}');
  },

  isJSON: function() {
    var str = this;
    if (str.blank()) return false;
    str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
    return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
  },

  evalJSON: function(sanitize) {
    var json = this.unfilterJSON();
    try {
      if (!sanitize || json.isJSON()) return eval('(' + json + ')');
    } catch (e) { }
    throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
  },

  include: function(pattern) {
    return this.indexOf(pattern) > -1;
  },

  startsWith: function(pattern) {
    return this.indexOf(pattern) === 0;
  },

  endsWith: function(pattern) {
    var d = this.length - pattern.length;
    return d >= 0 && this.lastIndexOf(pattern) === d;
  },

  empty: function() {
    return this == '';
  },

  blank: function() {
    return /^\s*$/.test(this);
  },

  interpolate: function(object, pattern) {
    return new Template(this, pattern).evaluate(object);
  }
});

if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, {
  escapeHTML: function() {
    return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
  },
  unescapeHTML: function() {
    return this.replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');
  }
});

String.prototype.gsub.prepareReplacement = function(replacement) {
  if (Object.isFunction(replacement)) return replacement;
  var template = new Template(replacement);
  return function(match) { return template.evaluate(match) };
};

String.prototype.parseQuery = String.prototype.toQueryParams;

Object.extend(String.prototype.escapeHTML, {
  div:  document.createElement('div'),
  text: document.createTextNode('')
});

with (String.prototype.escapeHTML) div.appendChild(text);

var Template = Class.create({
  initialize: function(template, pattern) {
    this.template = template.toString();
    this.pattern = pattern || Template.Pattern;
  },

  evaluate: function(object) {
    if (Object.isFunction(object.toTemplateReplacements))
      object = object.toTemplateReplacements();

    return this.template.gsub(this.pattern, function(match) {
      if (object == null) return '';

      var before = match[1] || '';
      if (before == '\\') return match[2];

      var ctx = object, expr = match[3];
      var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
      match = pattern.exec(expr);
      if (match == null) return before;

      while (match != null) {
        var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1];
        ctx = ctx[comp];
        if (null == ctx || '' == match[3]) break;
        expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
        match = pattern.exec(expr);
      }

      return before + String.interpret(ctx);
    });
  }
});
Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;

var $break = { };

var Enumerable = {
  each: function(iterator, context) {
    var index = 0;
    iterator = iterator.bind(context);
    try {
      this._each(function(value) {
        iterator(value, index++);
      });
    } catch (e) {
      if (e != $break) throw e;
    }
    return this;
  },

  eachSlice: function(number, iterator, context) {
    iterator = iterator ? iterator.bind(context) : Prototype.K;
    var index = -number, slices = [], array = this.toArray();
    while ((index += number) < array.length)
      slices.push(array.slice(index, index+number));
    return slices.collect(iterator, context);
  },

  all: function(iterator, context) {
    iterator = iterator ? iterator.bind(context) : Prototype.K;
    var result = true;
    this.each(function(value, index) {
      result = result && !!iterator(value, index);
      if (!result) throw $break;
    });
    return result;
  },

  any: function(iterator, context) {
    iterator = iterator ? iterator.bind(context) : Prototype.K;
    var result = false;
    this.each(function(value, index) {
      if (result = !!iterator(value, index))
        throw $break;
    });
    return result;
  },

  collect: function(iterator, context) {
    iterator = iterator ? iterator.bind(context) : Prototype.K;
    var results = [];
    this.each(function(value, index) {
      results.push(iterator(value, index));
    });
    return results;
  },

  detect: function(iterator, context) {
    iterator = iterator.bind(context);
    var result;
    this.each(function(value, index) {
      if (iterator(value, index)) {
        result = value;
        throw $break;
      }
    });
    return result;
  },

  findAll: function(iterator, context) {
    iterator = iterator.bind(context);
    var results = [];
    this.each(function(value, index) {
      if (iterator(value, index))
        results.push(value);
    });
    return results;
  },

  grep: function(filter, iterator, context) {
    iterator = iterator ? iterator.bind(context) : Prototype.K;
    var results = [];

    if (Object.isString(filter))
      filter = new RegExp(filter);

    this.each(function(value, index) {
      if (filter.match(value))
        results.push(iterator(value, index));
    });
    return results;
  },

  include: function(object) {
    if (Object.isFunction(this.indexOf))
      if (this.indexOf(object) != -1) return true;

    var found = false;
    this.each(function(value) {
      if (value == object) {
        found = true;
        throw $break;
      }
    });
    return found;
  },

  inGroupsOf: function(number, fillWith) {
    fillWith = Object.isUndefined(fillWith) ? null : fillWith;
    return this.eachSlice(number, function(slice) {
      while(slice.length < number) slice.push(fillWith);
      return slice;
    });
  },

  inject: function(memo, iterator, context) {
    iterator = iterator.bind(context);
    this.each(function(value, index) {
      memo = iterator(memo, value, index);
    });
    return memo;
  },

  invoke: function(method) {
    var args = $A(arguments).slice(1);
    return this.map(function(value) {
      return value[method].apply(value, args);
    });
  },

  max: function(iterator, context) {
    iterator = iterator ? iterator.bind(context) : Prototype.K;
    var result;
    this.each(function(value, index) {
      value = iterator(value, index);
      if (result == null || value >= result)
        result = value;
    });
    return result;
  },

  min: function(iterator, context) {
    iterator = iterator ? iterator.bind(context) : Prototype.K;
    var result;
    this.each(function(value, index) {
      value = iterator(value, index);
      if (result == null || value < result)
        result = value;
    });
    return result;
  },

  partition: function(iterator, context) {
    iterator = iterator ? iterator.bind(context) : Prototype.K;
    var trues = [], falses = [];
    this.each(function(value, index) {
      (iterator(value, index) ?
        trues : falses).push(value);
    });
    return [trues, falses];
  },

  pluck: function(property) {
    var results = [];
    this.each(function(value) {
      results.push(value[property]);
    });
    return results;
  },

  reject: function(iterator, context) {
    iterator = iterator.bind(context);
    var results = [];
    this.each(function(value, index) {
      if (!iterator(value, index))
        results.push(value);
    });
    return results;
  },

  sortBy: function(iterator, context) {
    iterator = iterator.bind(context);
    return this.map(function(value, index) {
      return {value: value, criteria: iterator(value, index)};
    }).sort(function(left, right) {
      var a = left.criteria, b = right.criteria;
      return a < b ? -1 : a > b ? 1 : 0;
    }).pluck('value');
  },

  toArray: function() {
    return this.map();
  },

  zip: function() {
    var iterator = Prototype.K, args = $A(arguments);
    if (Object.isFunction(args.last()))
      iterator = args.pop();

    var collections = [this].concat(args).map($A);
    return this.map(function(value, index) {
      return iterator(collections.pluck(index));
    });
  },

  size: function() {
    return this.toArray().length;
  },

  inspect: function() {
    return '#<Enumerable:' + this.toArray().inspect() + '>';
  }
};

Object.extend(Enumerable, {
  map:     Enumerable.collect,
  find:    Enumerable.detect,
  select:  Enumerable.findAll,
  filter:  Enumerable.findAll,
  member:  Enumerable.include,
  entries: Enumerable.toArray,
  every:   Enumerable.all,
  some:    Enumerable.any
});
function $A(iterable) {
  if (!iterable) return [];
  if (iterable.toArray) return iterable.toArray();
  var length = iterable.length || 0, results = new Array(length);
  while (length--) results[length] = iterable[length];
  return results;
}

if (Prototype.Browser.WebKit) {
  $A = function(iterable) {
    if (!iterable) return [];
    if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') &&
        iterable.toArray) return iterable.toArray();
    var length = iterable.length || 0, results = new Array(length);
    while (length--) results[length] = iterable[length];
    return results;
  };
}

Array.from = $A;

Object.extend(Array.prototype, Enumerable);

if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse;

Object.extend(Array.prototype, {
  _each: function(iterator) {
    for (var i = 0, length = this.length; i < length; i++)
      iterator(this[i]);
  },

  clear: function() {
    this.length = 0;
    return this;
  },

  first: function() {
    return this[0];
  },

  last: function() {
    return this[this.length - 1];
  },

  compact: function() {
    return this.select(function(value) {
      return value != null;
    });
  },

  flatten: function() {
    return this.inject([], function(array, value) {
      return array.concat(Object.isArray(value) ?
        value.flatten() : [value]);
    });
  },

  without: function() {
    var values = $A(arguments);
    return this.select(function(value) {
      return !values.include(value);
    });
  },

  reverse: function(inline) {
    return (inline !== false ? this : this.toArray())._reverse();
  },

  reduce: function() {
    return this.length > 1 ? this : this[0];
  },

  uniq: function(sorted) {
    return this.inject([], function(array, value, index) {
      if (0 == index || (sorted ? array.last() != value : !array.include(value)))
        array.push(value);
      return array;
    });
  },

  intersect: function(array) {
    return this.uniq().findAll(function(item) {
      return array.detect(function(value) { return item === value });
    });
  },

  clone: function() {
    return [].concat(this);
  },

  size: function() {
    return this.length;
  },

  inspect: function() {
    return '[' + this.map(Object.inspect).join(', ') + ']';
  },

  toJSON: function() {
    var results = [];
    this.each(function(object) {
      var value = Object.toJSON(object);
      if (!Object.isUndefined(value)) results.push(value);
    });
    return '[' + results.join(', ') + ']';
  }
});

// use native browser JS 1.6 implementation if available
if (Object.isFunction(Array.prototype.forEach))
  Array.prototype._each = Array.prototype.forEach;

if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) {
  i || (i = 0);
  var length = this.length;
  if (i < 0) i = length + i;
  for (; i < length; i++)
    if (this[i] === item) return i;
  return -1;
};

if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) {
  i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
  var n = this.slice(0, i).reverse().indexOf(item);
  return (n < 0) ? n : i - n - 1;
};

Array.prototype.toArray = Array.prototype.clone;

function $w(string) {
  if (!Object.isString(string)) return [];
  string = string.strip();
  return string ? string.split(/\s+/) : [];
}

if (Prototype.Browser.Opera){
  Array.prototype.concat = function() {
    var array = [];
    for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);
    for (var i = 0, length = arguments.length; i < length; i++) {
      if (Object.isArray(arguments[i])) {
        for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
          array.push(arguments[i][j]);
      } else {
        array.push(arguments[i]);
      }
    }
    return array;
  };
}
Object.extend(Number.prototype, {
  toColorPart: function() {
    return this.toPaddedString(2, 16);
  },

  succ: function() {
    return this + 1;
  },

  times: function(iterator) {
    $R(0, this, true).each(iterator);
    return this;
  },

  toPaddedString: function(length, radix) {
    var string = this.toString(radix || 10);
    return '0'.times(length - string.length) + string;
  },

  toJSON: function() {
    return isFinite(this) ? this.toString() : 'null';
  }
});

$w('abs round ceil floor').each(function(method){
  Number.prototype[method] = Math[method].methodize();
});
function $H(object) {
  return new Hash(object);
};

var Hash = Class.create(Enumerable, (function() {

  function toQueryPair(key, value) {
    if (Object.isUndefined(value)) return key;
    return key + '=' + encodeURIComponent(String.interpret(value));
  }

  return {
    initialize: function(object) {
      this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
    },

    _each: function(iterator) {
      for (var key in this._object) {
        var value = this._object[key], pair = [key, value];
        pair.key = key;
        pair.value = value;
        iterator(pair);
      }
    },

    set: function(key, value) {
      return this._object[key] = value;
    },

    get: function(key) {
      return this._object[key];
    },

    unset: function(key) {
      var value = this._object[key];
      delete this._object[key];
      return value;
    },

    toObject: function() {
      return Object.clone(this._object);
    },

    keys: function() {
      return this.pluck('key');
    },

    values: function() {
      return this.pluck('value');
    },

    index: function(value) {
      var match = this.detect(function(pair) {
        return pair.value === value;
      });
      return match && match.key;
    },

    merge: function(object) {
      return this.clone().update(object);
    },

    update: function(object) {
      return new Hash(object).inject(this, function(result, pair) {
        result.set(pair.key, pair.value);
        return result;
      });
    },

    toQueryString: function() {
      return this.map(function(pair) {
        var key = encodeURIComponent(pair.key), values = pair.value;

        if (values && typeof values == 'object') {
          if (Object.isArray(values))
            return values.map(toQueryPair.curry(key)).join('&');
        }
        return toQueryPair(key, values);
      }).join('&');
    },

    inspect: function() {
      return '#<Hash:{' + this.map(function(pair) {
        return pair.map(Object.inspect).join(': ');
      }).join(', ') + '}>';
    },

    toJSON: function() {
      return Object.toJSON(this.toObject());
    },

    clone: function() {
      return new Hash(this);
    }
  }
})());

Hash.prototype.toTemplateReplacements = Hash.prototype.toObject;
Hash.from = $H;
var ObjectRange = Class.create(Enumerable, {
  initialize: function(start, end, exclusive) {
    this.start = start;
    this.end = end;
    this.exclusive = exclusive;
  },

  _each: function(iterator) {
    var value = this.start;
    while (this.include(value)) {
      iterator(value);
      value = value.succ();
    }
  },

  include: function(value) {
    if (value < this.start)
      return false;
    if (this.exclusive)
      return value < this.end;
    return value <= this.end;
  }
});

var $R = function(start, end, exclusive) {
  return new ObjectRange(start, end, exclusive);
};

var Ajax = {
  getTransport: function() {
    return Try.these(
      function() {return new XMLHttpRequest()},
      function() {return new ActiveXObject('Msxml2.XMLHTTP')},
      function() {return new ActiveXObject('Microsoft.XMLHTTP')}
    ) || false;
  },

  activeRequestCount: 0
};

Ajax.Responders = {
  responders: [],

  _each: function(iterator) {
    this.responders._each(iterator);
  },

  register: function(responder) {
    if (!this.include(responder))
      this.responders.push(responder);
  },

  unregister: function(responder) {
    this.responders = this.responders.without(responder);
  },

  dispatch: function(callback, request, transport, json) {
    this.each(function(responder) {
      if (Object.isFunction(responder[callback])) {
        try {
          responder[callback].apply(responder, [request, transport, json]);
        } catch (e) { }
      }
    });
  }
};

Object.extend(Ajax.Responders, Enumerable);

Ajax.Responders.register({
  onCreate:   function() { Ajax.activeRequestCount++ },
  onComplete: function() { Ajax.activeRequestCount-- }
});

Ajax.Base = Class.create({
  initialize: function(options) {
    this.options = {
      method:       'post',
      asynchronous: true,
      contentType:  'application/x-www-form-urlencoded',
      encoding:     'UTF-8',
      parameters:   '',
      evalJSON:     true,
      evalJS:       true
    };
    Object.extend(this.options, options || { });

    this.options.method = this.options.method.toLowerCase();

    if (Object.isString(this.options.parameters))
      this.options.parameters = this.options.parameters.toQueryParams();
    else if (Object.isHash(this.options.parameters))
      this.options.parameters = this.options.parameters.toObject();
  }
});

Ajax.Request = Class.create(Ajax.Base, {
  _complete: false,

  initialize: function($super, url, options) {
    $super(options);
    this.transport = Ajax.getTransport();
    this.request(url);
  },

  request: function(url) {
    this.url = url;
    this.method = this.options.method;
    var params = Object.clone(this.options.parameters);

    if (!['get', 'post'].include(this.method)) {
      // simulate other verbs over post
      params['_method'] = this.method;
      this.method = 'post';
    }

    this.parameters = params;

    if (params = Object.toQueryString(params)) {
      // when GET, append parameters to URL
      if (this.method == 'get')
        this.url += (this.url.include('?') ? '&' : '?') + params;
      else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
        params += '&_=';
    }

    try {
      var response = new Ajax.Response(this);
      if (this.options.onCreate) this.options.onCreate(response);
      Ajax.Responders.dispatch('onCreate', this, response);

      this.transport.open(this.method.toUpperCase(), this.url,
        this.options.asynchronous);

      if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);

      this.transport.onreadystatechange = this.onStateChange.bind(this);
      this.setRequestHeaders();

      this.body = this.method == 'post' ? (this.options.postBody || params) : null;
      this.transport.send(this.body);

      /* Force Firefox to handle ready state 4 for synchronous requests */
      if (!this.options.asynchronous && this.transport.overrideMimeType)
        this.onStateChange();

    }
    catch (e) {
      this.dispatchException(e);
    }
  },

  onStateChange: function() {
    var readyState = this.transport.readyState;
    if (readyState > 1 && !((readyState == 4) && this._complete))
      this.respondToReadyState(this.transport.readyState);
  },

  setRequestHeaders: function() {
    var headers = {
      'X-Requested-With': 'XMLHttpRequest',
      'X-Prototype-Version': Prototype.Version,
      'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
    };

    if (this.method == 'post') {
      headers['Content-type'] = this.options.contentType +
        (this.options.encoding ? '; charset=' + this.options.encoding : '');

      /* Force "Connection: close" for older Mozilla browsers to work
       * around a bug where XMLHttpRequest sends an incorrect
       * Content-length header. See Mozilla Bugzilla #246651.
       */
      if (this.transport.overrideMimeType &&
          (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
            headers['Connection'] = 'close';
    }

    // user-defined headers
    if (typeof this.options.requestHeaders == 'object') {
      var extras = this.options.requestHeaders;

      if (Object.isFunction(extras.push))
        for (var i = 0, length = extras.length; i < length; i += 2)
          headers[extras[i]] = extras[i+1];
      else
        $H(extras).each(function(pair) { headers[pair.key] = pair.value });
    }

    for (var name in headers)
      this.transport.setRequestHeader(name, headers[name]);
  },

  success: function() {
    var status = this.getStatus();
    return !status || (status >= 200 && status < 300);
  },

  getStatus: function() {
    try {
      return this.transport.status || 0;
    } catch (e) { return 0 }
  },

  respondToReadyState: function(readyState) {
    var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);

    if (state == 'Complete') {
      try {
        this._complete = true;
        (this.options['on' + response.status]
         || this.options['on' + (this.success() ? 'Success' : 'Failure')]
         || Prototype.emptyFunction)(response, response.headerJSON);
      } catch (e) {
        this.dispatchException(e);
      }

      var contentType = response.getHeader('Content-type');
      if (this.options.evalJS == 'force'
          || (this.options.evalJS && this.isSameOrigin() && contentType
          && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
        this.evalResponse();
    }

    try {
      (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
      Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
    } catch (e) {
      this.dispatchException(e);
    }

    if (state == 'Complete') {
      // avoid memory leak in MSIE: clean up
      this.transport.onreadystatechange = Prototype.emptyFunction;
    }
  },

  isSameOrigin: function() {
    var m = this.url.match(/^\s*https?:\/\/[^\/]*/);
    return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({
      protocol: location.protocol,
      domain: document.domain,
      port: location.port ? ':' + location.port : ''
    }));
  },

  getHeader: function(name) {
    try {
      return this.transport.getResponseHeader(name) || null;
    } catch (e) { return null }
  },

  evalResponse: function() {
    try {
      return eval((this.transport.responseText || '').unfilterJSON());
    } catch (e) {
      this.dispatchException(e);
    }
  },

  dispatchException: function(exception) {
    (this.options.onException || Prototype.emptyFunction)(this, exception);
    Ajax.Responders.dispatch('onException', this, exception);
  }
});

Ajax.Request.Events =
  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];

Ajax.Response = Class.create({
  initialize: function(request){
    this.request = request;
    var transport  = this.transport  = request.transport,
        readyState = this.readyState = transport.readyState;

    if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
      this.status       = this.getStatus();
      this.statusText   = this.getStatusText();
      this.responseText = String.interpret(transport.responseText);
      this.headerJSON   = this._getHeaderJSON();
    }

    if(readyState == 4) {
      var xml = transport.responseXML;
      this.responseXML  = Object.isUndefined(xml) ? null : xml;
      this.responseJSON = this._getResponseJSON();
    }
  },

  status:      0,
  statusText: '',

  getStatus: Ajax.Request.prototype.getStatus,

  getStatusText: function() {
    try {
      return this.transport.statusText || '';
    } catch (e) { return '' }
  },

  getHeader: Ajax.Request.prototype.getHeader,

  getAllHeaders: function() {
    try {
      return this.getAllResponseHeaders();
    } catch (e) { return null }
  },

  getResponseHeader: function(name) {
    return this.transport.getResponseHeader(name);
  },

  getAllResponseHeaders: function() {
    return this.transport.getAllResponseHeaders();
  },

  _getHeaderJSON: function() {
    var json = this.getHeader('X-JSON');
    if (!json) return null;
    json = decodeURIComponent(escape(json));
    try {
      return json.evalJSON(this.request.options.sanitizeJSON ||
        !this.request.isSameOrigin());
    } catch (e) {
      this.request.dispatchException(e);
    }
  },

  _getResponseJSON: function() {
    var options = this.request.options;
    if (!options.evalJSON || (options.evalJSON != 'force' &&
      !(this.getHeader('Content-type') || '').include('application/json')) ||
        this.responseText.blank())
          return null;
    try {
      return this.responseText.evalJSON(options.sanitizeJSON ||
        !this.request.isSameOrigin());
    } catch (e) {
      this.request.dispatchException(e);
    }
  }
});

Ajax.Updater = Class.create(Ajax.Request, {
  initialize: function($super, container, url, options) {
    this.container = {
      success: (container.success || container),
      failure: (container.failure || (container.success ? null : container))
    };

    options = Object.clone(options);
    var onComplete = options.onComplete;
    options.onComplete = (function(response, json) {
      this.updateContent(response.responseText);
      if (Object.isFunction(onComplete)) onComplete(response, json);
    }).bind(this);

    $super(url, options);
  },

  updateContent: function(responseText) {
    var receiver = this.container[this.success() ? 'success' : 'failure'],
        options = this.options;

    if (!options.evalScripts) responseText = responseText.stripScripts();

    if (receiver = $(receiver)) {
      if (options.insertion) {
        if (Object.isString(options.insertion)) {
          var insertion = { }; insertion[options.insertion] = responseText;
          receiver.insert(insertion);
        }
        else options.insertion(receiver, responseText);
      }
      else receiver.update(responseText);
    }
  }
});

Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
  initialize: function($super, container, url, options) {
    $super(options);
    this.onComplete = this.options.onComplete;

    this.frequency = (this.options.frequency || 2);
    this.decay = (this.options.decay || 1);

    this.updater = { };
    this.container = container;
    this.url = url;

    this.start();
  },

  start: function() {
    this.options.onComplete = this.updateComplete.bind(this);
    this.onTimerEvent();
  },

  stop: function() {
    this.updater.options.onComplete = undefined;
    clearTimeout(this.timer);
    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
  },

  updateComplete: function(response) {
    if (this.options.decay) {
      this.decay = (response.responseText == this.lastText ?
        this.decay * this.options.decay : 1);

      this.lastText = response.responseText;
    }
    this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
  },

  onTimerEvent: function() {
    this.updater = new Ajax.Updater(this.container, this.url, this.options);
  }
});
function $(element) {
  if (arguments.length > 1) {
    for (var i = 0, elements = [], length = arguments.length; i < length; i++)
      elements.push($(arguments[i]));
    return elements;
  }
  if (Object.isString(element))
    element = document.getElementById(element);
  return Element.extend(element);
}

if (Prototype.BrowserFeatures.XPath) {
  document._getElementsByXPath = function(expression, parentElement) {
    var results = [];
    var query = document.evaluate(expression, $(parentElement) || document,
      null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
    for (var i = 0, length = query.snapshotLength; i < length; i++)
      results.push(Element.extend(query.snapshotItem(i)));
    return results;
  };
}

/*--------------------------------------------------------------------------*/

if (!window.Node) var Node = { };

if (!Node.ELEMENT_NODE) {
  // DOM level 2 ECMAScript Language Binding
  Object.extend(Node, {
    ELEMENT_NODE: 1,
    ATTRIBUTE_NODE: 2,
    TEXT_NODE: 3,
    CDATA_SECTION_NODE: 4,
    ENTITY_REFERENCE_NODE: 5,
    ENTITY_NODE: 6,
    PROCESSING_INSTRUCTION_NODE: 7,
    COMMENT_NODE: 8,
    DOCUMENT_NODE: 9,
    DOCUMENT_TYPE_NODE: 10,
    DOCUMENT_FRAGMENT_NODE: 11,
    NOTATION_NODE: 12
  });
}

(function() {
  var element = this.Element;
  this.Element = function(tagName, attributes) {
    attributes = attributes || { };
    tagName = tagName.toLowerCase();
    var cache = Element.cache;
    if (Prototype.Browser.IE && attributes.name) {
      tagName = '<' + tagName + ' name="' + attributes.name + '">';
      delete attributes.name;
      return Element.writeAttribute(document.createElement(tagName), attributes);
    }
    if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
    return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
  };
  Object.extend(this.Element, element || { });
}).call(window);

Element.cache = { };

Element.Methods = {
  visible: function(element) {
    return $(element).style.display != 'none';
  },

  toggle: function(element) {
    element = $(element);
    Element[Element.visible(element) ? 'hide' : 'show'](element);
    return element;
  },

  hide: function(element) {
    $(element).style.display = 'none';
    return element;
  },

  show: function(element) {
    $(element).style.display = '';
    return element;
  },

  remove: function(element) {
    element = $(element);
    element.parentNode.removeChild(element);
    return element;
  },

  update: function(element, content) {
    element = $(element);
    if (content && content.toElement) content = content.toElement();
    if (Object.isElement(content)) return element.update().insert(content);
    content = Object.toHTML(content);
    element.innerHTML = content.stripScripts();
    content.evalScripts.bind(content).defer();
    return element;
  },

  replace: function(element, content) {
    element = $(element);
    if (content && content.toElement) content = content.toElement();
    else if (!Object.isElement(content)) {
      content = Object.toHTML(content);
      var range = element.ownerDocument.createRange();
      range.selectNode(element);
      content.evalScripts.bind(content).defer();
      content = range.createContextualFragment(content.stripScripts());
    }
    element.parentNode.replaceChild(content, element);
    return element;
  },

  insert: function(element, insertions) {
    element = $(element);

    if (Object.isString(insertions) || Object.isNumber(insertions) ||
        Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
          insertions = {bottom:insertions};

    var content, insert, tagName, childNodes;

    for (var position in insertions) {
      content  = insertions[position];
      position = position.toLowerCase();
      insert = Element._insertionTranslations[position];

      if (content && content.toElement) content = content.toElement();
      if (Object.isElement(content)) {
        insert(element, content);
        continue;
      }

      content = Object.toHTML(content);

      tagName = ((position == 'before' || position == 'after')
        ? element.parentNode : element).tagName.toUpperCase();

      childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());

      if (position == 'top' || position == 'after') childNodes.reverse();
      childNodes.each(insert.curry(element));

      content.evalScripts.bind(content).defer();
    }

    return element;
  },

  wrap: function(element, wrapper, attributes) {
    element = $(element);
    if (Object.isElement(wrapper))
      $(wrapper).writeAttribute(attributes || { });
    else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);
    else wrapper = new Element('div', wrapper);
    if (element.parentNode)
      element.parentNode.replaceChild(wrapper, element);
    wrapper.appendChild(element);
    return wrapper;
  },

  inspect: function(element) {
    element = $(element);
    var result = '<' + element.tagName.toLowerCase();
    $H({'id': 'id', 'className': 'class'}).each(function(pair) {
      var property = pair.first(), attribute = pair.last();
      var value = (element[property] || '').toString();
      if (value) result += ' ' + attribute + '=' + value.inspect(true);
    });
    return result + '>';
  },

  recursivelyCollect: function(element, property) {
    element = $(element);
    var elements = [];
    while (element = element[property])
      if (element.nodeType == 1)
        elements.push(Element.extend(element));
    return elements;
  },

  ancestors: function(element) {
    return $(element).recursivelyCollect('parentNode');
  },

  descendants: function(element) {
    return $(element).select("*");
  },

  firstDescendant: function(element) {
    element = $(element).firstChild;
    while (element && element.nodeType != 1) element = element.nextSibling;
    return $(element);
  },

  immediateDescendants: function(element) {
    if (!(element = $(element).firstChild)) return [];
    while (element && element.nodeType != 1) element = element.nextSibling;
    if (element) return [element].concat($(element).nextSiblings());
    return [];
  },

  previousSiblings: function(element) {
    return $(element).recursivelyCollect('previousSibling');
  },

  nextSiblings: function(element) {
    return $(element).recursivelyCollect('nextSibling');
  },

  siblings: function(element) {
    element = $(element);
    return element.previousSiblings().reverse().concat(element.nextSiblings());
  },

  match: function(element, selector) {
    if (Object.isString(selector))
      selector = new Selector(selector);
    return selector.match($(element));
  },

  up: function(element, expression, index) {
    element = $(element);
    if (arguments.length == 1) return $(element.parentNode);
    var ancestors = element.ancestors();
    return Object.isNumber(expression) ? ancestors[expression] :
      Selector.findElement(ancestors, expression, index);
  },

  down: function(element, expression, index) {
    element = $(element);
    if (arguments.length == 1) return element.firstDescendant();
    return Object.isNumber(expression) ? element.descendants()[expression] :
      element.select(expression)[index || 0];
  },

  previous: function(element, expression, index) {
    element = $(element);
    if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
    var previousSiblings = element.previousSiblings();
    return Object.isNumber(expression) ? previousSiblings[expression] :
      Selector.findElement(previousSiblings, expression, index);
  },

  next: function(element, expression, index) {
    element = $(element);
    if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
    var nextSiblings = element.nextSiblings();
    return Object.isNumber(expression) ? nextSiblings[expression] :
      Selector.findElement(nextSiblings, expression, index);
  },

  select: function() {
    var args = $A(arguments), element = $(args.shift());
    return Selector.findChildElements(element, args);
  },

  adjacent: function() {
    var args = $A(arguments), element = $(args.shift());
    return Selector.findChildElements(element.parentNode, args).without(element);
  },

  identify: function(element) {
    element = $(element);
    var id = element.readAttribute('id'), self = arguments.callee;
    if (id) return id;
    do { id = 'anonymous_element_' + self.counter++ } while ($(id));
    element.writeAttribute('id', id);
    return id;
  },

  readAttribute: function(element, name) {
    element = $(element);
    if (Prototype.Browser.IE) {
      var t = Element._attributeTranslations.read;
      if (t.values[name]) return t.values[name](element, name);
      if (t.names[name]) name = t.names[name];
      if (name.include(':')) {
        return (!element.attributes || !element.attributes[name]) ? null :
         element.attributes[name].value;
      }
    }
    return element.getAttribute(name);
  },

  writeAttribute: function(element, name, value) {
    element = $(element);
    var attributes = { }, t = Element._attributeTranslations.write;

    if (typeof name == 'object') attributes = name;
    else attributes[name] = Object.isUndefined(value) ? true : value;

    for (var attr in attributes) {
      name = t.names[attr] || attr;
      value = attributes[attr];
      if (t.values[attr]) name = t.values[attr](element, value);
      if (value === false || value === null)
        element.removeAttribute(name);
      else if (value === true)
        element.setAttribute(name, name);
      else element.setAttribute(name, value);
    }
    return element;
  },

  getHeight: function(element) {
    return $(element).getDimensions().height;
  },

  getWidth: function(element) {
    return $(element).getDimensions().width;
  },

  classNames: function(element) {
    return new Element.ClassNames(element);
  },

  hasClassName: function(element, className) {
    if (!(element = $(element))) return;
    var elementClassName = element.className;
    return (elementClassName.length > 0 && (elementClassName == className ||
      new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
  },

  addClassName: function(element, className) {
    if (!(element = $(element))) return;
    if (!element.hasClassName(className))
      element.className += (element.className ? ' ' : '') + className;
    return element;
  },

  removeClassName: function(element, className) {
    if (!(element = $(element))) return;
    element.className = element.className.replace(
      new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
    return element;
  },

  toggleClassName: function(element, className) {
    if (!(element = $(element))) return;
    return element[element.hasClassName(className) ?
      'removeClassName' : 'addClassName'](className);
  },

  // removes whitespace-only text node children
  cleanWhitespace: function(element) {
    element = $(element);
    var node = element.firstChild;
    while (node) {
      var nextNode = node.nextSibling;
      if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
        element.removeChild(node);
      node = nextNode;
    }
    return element;
  },

  empty: function(element) {
    return $(element).innerHTML.blank();
  },

  descendantOf: function(element, ancestor) {
    element = $(element), ancestor = $(ancestor);
    var originalAncestor = ancestor;

    if (element.compareDocumentPosition)
      return (element.compareDocumentPosition(ancestor) & 8) === 8;

    if (element.sourceIndex && !Prototype.Browser.Opera) {
      var e = element.sourceIndex, a = ancestor.sourceIndex,
       nextAncestor = ancestor.nextSibling;
      if (!nextAncestor) {
        do { ancestor = ancestor.parentNode; }
        while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode);
      }
      if (nextAncestor && nextAncestor.sourceIndex)
       return (e > a && e < nextAncestor.sourceIndex);
    }

    while (element = element.parentNode)
      if (element == originalAncestor) return true;
    return false;
  },

  scrollTo: function(element) {
    element = $(element);
    var pos = element.cumulativeOffset();
    window.scrollTo(pos[0], pos[1]);
    return element;
  },

  getStyle: function(element, style) {
    element = $(element);
    style = style == 'float' ? 'cssFloat' : style.camelize();
    var value = element.style[style];
    if (!value) {
      var css = document.defaultView.getComputedStyle(element, null);
      value = css ? css[style] : null;
    }
    if (style == 'opacity') return value ? parseFloat(value) : 1.0;
    return value == 'auto' ? null : value;
  },

  getOpacity: function(element) {
    return $(element).getStyle('opacity');
  },

  setStyle: function(element, styles) {
    element = $(element);
    var elementStyle = element.style, match;
    if (Object.isString(styles)) {
      element.style.cssText += ';' + styles;
      return styles.include('opacity') ?
        element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;
    }
    for (var property in styles)
      if (property == 'opacity') element.setOpacity(styles[property]);
      else
        elementStyle[(property == 'float' || property == 'cssFloat') ?
          (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') :
            property] = styles[property];

    return element;
  },

  setOpacity: function(element, value) {
    element = $(element);
    element.style.opacity = (value == 1 || value === '') ? '' :
      (value < 0.00001) ? 0 : value;
    return element;
  },

  getDimensions: function(element) {
    element = $(element);
    var display = $(element).getStyle('display');
    if (display != 'none' && display != null) // Safari bug
      return {width: element.offsetWidth, height: element.offsetHeight};

    // All *Width and *Height properties give 0 on elements with display none,
    // so enable the element temporarily
    var els = element.style;
    var originalVisibility = els.visibility;
    var originalPosition = els.position;
    var originalDisplay = els.display;
    els.visibility = 'hidden';
    els.position = 'absolute';
    els.display = 'block';
    var originalWidth = element.clientWidth;
    var originalHeight = element.clientHeight;
    els.display = originalDisplay;
    els.position = originalPosition;
    els.visibility = originalVisibility;
    return {width: originalWidth, height: originalHeight};
  },

  makePositioned: function(element) {
    element = $(element);
    var pos = Element.getStyle(element, 'position');
    if (pos == 'static' || !pos) {
      element._madePositioned = true;
      element.style.position = 'relative';
      // Opera returns the offset relative to the positioning context, when an
      // element is position relative but top and left have not been defined
      if (window.opera) {
        element.style.top = 0;
        element.style.left = 0;
      }
    }
    return element;
  },

  undoPositioned: function(element) {
    element = $(element);
    if (element._madePositioned) {
      element._madePositioned = undefined;
      element.style.position =
        element.style.top =
        element.style.left =
        element.style.bottom =
        element.style.right = '';
    }
    return element;
  },

  makeClipping: function(element) {
    element = $(element);
    if (element._overflow) return element;
    element._overflow = Element.getStyle(element, 'overflow') || 'auto';
    if (element._overflow !== 'hidden')
      element.style.overflow = 'hidden';
    return element;
  },

  undoClipping: function(element) {
    element = $(element);
    if (!element._overflow) return element;
    element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
    element._overflow = null;
    return element;
  },

  cumulativeOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      element = element.offsetParent;
    } while (element);
    return Element._returnOffset(valueL, valueT);
  },

  positionedOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      element = element.offsetParent;
      if (element) {
        if (element.tagName == 'BODY') break;
        var p = Element.getStyle(element, 'position');
        if (p !== 'static') break;
      }
    } while (element);
    return Element._returnOffset(valueL, valueT);
  },

  absolutize: function(element) {
    element = $(element);
    if (element.getStyle('position') == 'absolute') return;
    // Position.prepare(); // To be done manually by Scripty when it needs it.

    var offsets = element.positionedOffset();
    var top     = offsets[1];
    var left    = offsets[0];
    var width   = element.clientWidth;
    var height  = element.clientHeight;

    element._originalLeft   = left - parseFloat(element.style.left  || 0);
    element._originalTop    = top  - parseFloat(element.style.top || 0);
    element._originalWidth  = element.style.width;
    element._originalHeight = element.style.height;

    element.style.position = 'absolute';
    element.style.top    = top + 'px';
    element.style.left   = left + 'px';
    element.style.width  = width + 'px';
    element.style.height = height + 'px';
    return element;
  },

  relativize: function(element) {
    element = $(element);
    if (element.getStyle('position') == 'relative') return;
    // Position.prepare(); // To be done manually by Scripty when it needs it.

    element.style.position = 'relative';
    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);

    element.style.top    = top + 'px';
    element.style.left   = left + 'px';
    element.style.height = element._originalHeight;
    element.style.width  = element._originalWidth;
    return element;
  },

  cumulativeScrollOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.scrollTop  || 0;
      valueL += element.scrollLeft || 0;
      element = element.parentNode;
    } while (element);
    return Element._returnOffset(valueL, valueT);
  },

  getOffsetParent: function(element) {
    if (element.offsetParent) return $(element.offsetParent);
    if (element == document.body) return $(element);

    while ((element = element.parentNode) && element != document.body)
      if (Element.getStyle(element, 'position') != 'static')
        return $(element);

    return $(document.body);
  },

  viewportOffset: function(forElement) {
    var valueT = 0, valueL = 0;

    var element = forElement;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;

      // Safari fix
      if (element.offsetParent == document.body &&
        Element.getStyle(element, 'position') == 'absolute') break;

    } while (element = element.offsetParent);

    element = forElement;
    do {
      if (!Prototype.Browser.Opera || element.tagName == 'BODY') {
        valueT -= element.scrollTop  || 0;
        valueL -= element.scrollLeft || 0;
      }
    } while (element = element.parentNode);

    return Element._returnOffset(valueL, valueT);
  },

  clonePosition: function(element, source) {
    var options = Object.extend({
      setLeft:    true,
      setTop:     true,
      setWidth:   true,
      setHeight:  true,
      offsetTop:  0,
      offsetLeft: 0
    }, arguments[2] || { });

    // find page position of source
    source = $(source);
    var p = source.viewportOffset();

    // find coordinate system to use
    element = $(element);
    var delta = [0, 0];
    var parent = null;
    // delta [0,0] will do fine with position: fixed elements,
    // position:absolute needs offsetParent deltas
    if (Element.getStyle(element, 'position') == 'absolute') {
      parent = element.getOffsetParent();
      delta = parent.viewportOffset();
    }

    // correct by body offsets (fixes Safari)
    if (parent == document.body) {
      delta[0] -= document.body.offsetLeft;
      delta[1] -= document.body.offsetTop;
    }

    // set position
    if (options.setLeft)   element.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
    if (options.setTop)    element.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
    if (options.setWidth)  element.style.width = source.offsetWidth + 'px';
    if (options.setHeight) element.style.height = source.offsetHeight + 'px';
    return element;
  }
};

Element.Methods.identify.counter = 1;

Object.extend(Element.Methods, {
  getElementsBySelector: Element.Methods.select,
  childElements: Element.Methods.immediateDescendants
});

Element._attributeTranslations = {
  write: {
    names: {
      className: 'class',
      htmlFor:   'for'
    },
    values: { }
  }
};

if (Prototype.Browser.Opera) {
  Element.Methods.getStyle = Element.Methods.getStyle.wrap(
    function(proceed, element, style) {
      switch (style) {
        case 'left': case 'top': case 'right': case 'bottom':
          if (proceed(element, 'position') === 'static') return null;
        case 'height': case 'width':
          // returns '0px' for hidden elements; we want it to return null
          if (!Element.visible(element)) return null;

          // returns the border-box dimensions rather than the content-box
          // dimensions, so we subtract padding and borders from the value
          var dim = parseInt(proceed(element, style), 10);

          if (dim !== element['offset' + style.capitalize()])
            return dim + 'px';

          var properties;
          if (style === 'height') {
            properties = ['border-top-width', 'padding-top',
             'padding-bottom', 'border-bottom-width'];
          }
          else {
            properties = ['border-left-width', 'padding-left',
             'padding-right', 'border-right-width'];
          }
          return properties.inject(dim, function(memo, property) {
            var val = proceed(element, property);
            return val === null ? memo : memo - parseInt(val, 10);
          }) + 'px';
        default: return proceed(element, style);
      }
    }
  );

  Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(
    function(proceed, element, attribute) {
      if (attribute === 'title') return element.title;
      return proceed(element, attribute);
    }
  );
}

else if (Prototype.Browser.IE) {
  // IE doesn't report offsets correctly for static elements, so we change them
  // to "relative" to get the values, then change them back.
  Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(
    function(proceed, element) {
      element = $(element);
      var position = element.getStyle('position');
      if (position !== 'static') return proceed(element);
      element.setStyle({ position: 'relative' });
      var value = proceed(element);
      element.setStyle({ position: position });
      return value;
    }
  );

  $w('positionedOffset viewportOffset').each(function(method) {
    Element.Methods[method] = Element.Methods[method].wrap(
      function(proceed, element) {
        element = $(element);
        var position = element.getStyle('position');
        if (position !== 'static') return proceed(element);
        // Trigger hasLayout on the offset parent so that IE6 reports
        // accurate offsetTop and offsetLeft values for position: fixed.
        var offsetParent = element.getOffsetParent();
        if (offsetParent && offsetParent.getStyle('position') === 'fixed')
          offsetParent.setStyle({ zoom: 1 });
        element.setStyle({ position: 'relative' });
        var value = proceed(element);
        element.setStyle({ position: position });
        return value;
      }
    );
  });

  Element.Methods.getStyle = function(element, style) {
    element = $(element);
    style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
    var value = element.style[style];
    if (!value && element.currentStyle) value = element.currentStyle[style];

    if (style == 'opacity') {
      if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
        if (value[1]) return parseFloat(value[1]) / 100;
      return 1.0;
    }

    if (value == 'auto') {
      if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
        return element['offset' + style.capitalize()] + 'px';
      return null;
    }
    return value;
  };

  Element.Methods.setOpacity = function(element, value) {
    function stripAlpha(filter){
      return filter.replace(/alpha\([^\)]*\)/gi,'');
    }
    element = $(element);
    var currentStyle = element.currentStyle;
    if ((currentStyle && !currentStyle.hasLayout) ||
      (!currentStyle && element.style.zoom == 'normal'))
        element.style.zoom = 1;

    var filter = element.getStyle('filter'), style = element.style;
    if (value == 1 || value === '') {
      (filter = stripAlpha(filter)) ?
        style.filter = filter : style.removeAttribute('filter');
      return element;
    } else if (value < 0.00001) value = 0;
    style.filter = stripAlpha(filter) +
      'alpha(opacity=' + (value * 100) + ')';
    return element;
  };

  Element._attributeTranslations = {
    read: {
      names: {
        'class': 'className',
        'for':   'htmlFor'
      },
      values: {
        _getAttr: function(element, attribute) {
          return element.getAttribute(attribute, 2);
        },
        _getAttrNode: function(element, attribute) {
          var node = element.getAttributeNode(attribute);
          return node ? node.value : "";
        },
        _getEv: function(element, attribute) {
          attribute = element.getAttribute(attribute);
          return attribute ? attribute.toString().slice(23, -2) : null;
        },
        _flag: function(element, attribute) {
          return $(element).hasAttribute(attribute) ? attribute : null;
        },
        style: function(element) {
          return element.style.cssText.toLowerCase();
        },
        title: function(element) {
          return element.title;
        }
      }
    }
  };

  Element._attributeTranslations.write = {
    names: Object.extend({
      cellpadding: 'cellPadding',
      cellspacing: 'cellSpacing'
    }, Element._attributeTranslations.read.names),
    values: {
      checked: function(element, value) {
        element.checked = !!value;
      },

      style: function(element, value) {
        element.style.cssText = value ? value : '';
      }
    }
  };

  Element._attributeTranslations.has = {};

  $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
      'encType maxLength readOnly longDesc').each(function(attr) {
    Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;
    Element._attributeTranslations.has[attr.toLowerCase()] = attr;
  });

  (function(v) {
    Object.extend(v, {
      href:        v._getAttr,
      src:         v._getAttr,
      type:        v._getAttr,
      action:      v._getAttrNode,
      disabled:    v._flag,
      checked:     v._flag,
      readonly:    v._flag,
      multiple:    v._flag,
      onload:      v._getEv,
      onunload:    v._getEv,
      onclick:     v._getEv,
      ondblclick:  v._getEv,
      onmousedown: v._getEv,
      onmouseup:   v._getEv,
      onmouseover: v._getEv,
      onmousemove: v._getEv,
      onmouseout:  v._getEv,
      onfocus:     v._getEv,
      onblur:      v._getEv,
      onkeypress:  v._getEv,
      onkeydown:   v._getEv,
      onkeyup:     v._getEv,
      onsubmit:    v._getEv,
      onreset:     v._getEv,
      onselect:    v._getEv,
      onchange:    v._getEv
    });
  })(Element._attributeTranslations.read.values);
}

else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) {
  Element.Methods.setOpacity = function(element, value) {
    element = $(element);
    element.style.opacity = (value == 1) ? 0.999999 :
      (value === '') ? '' : (value < 0.00001) ? 0 : value;
    return element;
  };
}

else if (Prototype.Browser.WebKit) {
  Element.Methods.setOpacity = function(element, value) {
    element = $(element);
    element.style.opacity = (value == 1 || value === '') ? '' :
      (value < 0.00001) ? 0 : value;

    if (value == 1)
      if(element.tagName == 'IMG' && element.width) {
        element.width++; element.width--;
      } else try {
        var n = document.createTextNode(' ');
        element.appendChild(n);
        element.removeChild(n);
      } catch (e) { }

    return element;
  };

  // Safari returns margins on body which is incorrect if the child is absolutely
  // positioned.  For performance reasons, redefine Element#cumulativeOffset for
  // KHTML/WebKit only.
  Element.Methods.cumulativeOffset = function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      if (element.offsetParent == document.body)
        if (Element.getStyle(element, 'position') == 'absolute') break;

      element = element.offsetParent;
    } while (element);

    return Element._returnOffset(valueL, valueT);
  };
}

if (Prototype.Browser.IE || Prototype.Browser.Opera) {
  // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements
  Element.Methods.update = function(element, content) {
    element = $(element);

    if (content && content.toElement) content = content.toElement();
    if (Object.isElement(content)) return element.update().insert(content);

    content = Object.toHTML(content);
    var tagName = element.tagName.toUpperCase();

    if (tagName in Element._insertionTranslations.tags) {
      $A(element.childNodes).each(function(node) { element.removeChild(node) });
      Element._getContentFromAnonymousElement(tagName, content.stripScripts())
        .each(function(node) { element.appendChild(node) });
    }
    else element.innerHTML = content.stripScripts();

    content.evalScripts.bind(content).defer();
    return element;
  };
}

if ('outerHTML' in document.createElement('div')) {
  Element.Methods.replace = function(element, content) {
    element = $(element);

    if (content && content.toElement) content = content.toElement();
    if (Object.isElement(content)) {
      element.parentNode.replaceChild(content, element);
      return element;
    }

    content = Object.toHTML(content);
    var parent = element.parentNode, tagName = parent.tagName.toUpperCase();

    if (Element._insertionTranslations.tags[tagName]) {
      var nextSibling = element.next();
      var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
      parent.removeChild(element);
      if (nextSibling)
        fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
      else
        fragments.each(function(node) { parent.appendChild(node) });
    }
    else element.outerHTML = content.stripScripts();

    content.evalScripts.bind(content).defer();
    return element;
  };
}

Element._returnOffset = function(l, t) {
  var result = [l, t];
  result.left = l;
  result.top = t;
  return result;
};

Element._getContentFromAnonymousElement = function(tagName, html) {
  var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
  if (t) {
    div.innerHTML = t[0] + html + t[1];
    t[2].times(function() { div = div.firstChild });
  } else div.innerHTML = html;
  return $A(div.childNodes);
};

Element._insertionTranslations = {
  before: function(element, node) {
    element.parentNode.insertBefore(node, element);
  },
  top: function(element, node) {
    element.insertBefore(node, element.firstChild);
  },
  bottom: function(element, node) {
    element.appendChild(node);
  },
  after: function(element, node) {
    element.parentNode.insertBefore(node, element.nextSibling);
  },
  tags: {
    TABLE:  ['<table>',                '</table>',                   1],
    TBODY:  ['<table><tbody>',         '</tbody></table>',           2],
    TR:     ['<table><tbody><tr>',     '</tr></tbody></table>',      3],
    TD:     ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
    SELECT: ['<select>',               '</select>',                  1]
  }
};

(function() {
  Object.extend(this.tags, {
    THEAD: this.tags.TBODY,
    TFOOT: this.tags.TBODY,
    TH:    this.tags.TD
  });
}).call(Element._insertionTranslations);

Element.Methods.Simulated = {
  hasAttribute: function(element, attribute) {
    attribute = Element._attributeTranslations.has[attribute] || attribute;
    var node = $(element).getAttributeNode(attribute);
    return node && node.specified;
  }
};

Element.Methods.ByTag = { };

Object.extend(Element, Element.Methods);

if (!Prototype.BrowserFeatures.ElementExtensions &&
    document.createElement('div').__proto__) {
  window.HTMLElement = { };
  window.HTMLElement.prototype = document.createElement('div').__proto__;
  Prototype.BrowserFeatures.ElementExtensions = true;
}

Element.extend = (function() {
  if (Prototype.BrowserFeatures.SpecificElementExtensions)
    return Prototype.K;

  var Methods = { }, ByTag = Element.Methods.ByTag;

  var extend = Object.extend(function(element) {
    if (!element || element._extendedByPrototype ||
        element.nodeType != 1 || element == window) return element;

    var methods = Object.clone(Methods),
      tagName = element.tagName, property, value;

    // extend methods for specific tags
    if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);

    for (property in methods) {
      value = methods[property];
      if (Object.isFunction(value) && !(property in element))
        element[property] = value.methodize();
    }

    element._extendedByPrototype = Prototype.emptyFunction;
    return element;

  }, {
    refresh: function() {
      // extend methods for all tags (Safari doesn't need this)
      if (!Prototype.BrowserFeatures.ElementExtensions) {
        Object.extend(Methods, Element.Methods);
        Object.extend(Methods, Element.Methods.Simulated);
      }
    }
  });

  extend.refresh();
  return extend;
})();

Element.hasAttribute = function(element, attribute) {
  if (element.hasAttribute) return element.hasAttribute(attribute);
  return Element.Methods.Simulated.hasAttribute(element, attribute);
};

Element.addMethods = function(methods) {
  var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;

  if (!methods) {
    Object.extend(Form, Form.Methods);
    Object.extend(Form.Element, Form.Element.Methods);
    Object.extend(Element.Methods.ByTag, {
      "FORM":     Object.clone(Form.Methods),
      "INPUT":    Object.clone(Form.Element.Methods),
      "SELECT":   Object.clone(Form.Element.Methods),
      "TEXTAREA": Object.clone(Form.Element.Methods)
    });
  }

  if (arguments.length == 2) {
    var tagName = methods;
    methods = arguments[1];
  }

  if (!tagName) Object.extend(Element.Methods, methods || { });
  else {
    if (Object.isArray(tagName)) tagName.each(extend);
    else extend(tagName);
  }

  function extend(tagName) {
    tagName = tagName.toUpperCase();
    if (!Element.Methods.ByTag[tagName])
      Element.Methods.ByTag[tagName] = { };
    Object.extend(Element.Methods.ByTag[tagName], methods);
  }

  function copy(methods, destination, onlyIfAbsent) {
    onlyIfAbsent = onlyIfAbsent || false;
    for (var property in methods) {
      var value = methods[property];
      if (!Object.isFunction(value)) continue;
      if (!onlyIfAbsent || !(property in destination))
        destination[property] = value.methodize();
    }
  }

  function findDOMClass(tagName) {
    var klass;
    var trans = {
      "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
      "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
      "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
      "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
      "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
      "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
      "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
      "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
      "FrameSet", "IFRAME": "IFrame"
    };
    if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
    if (window[klass]) return window[klass];
    klass = 'HTML' + tagName + 'Element';
    if (window[klass]) return window[klass];
    klass = 'HTML' + tagName.capitalize() + 'Element';
    if (window[klass]) return window[klass];

    window[klass] = { };
    window[klass].prototype = document.createElement(tagName).__proto__;
    return window[klass];
  }

  if (F.ElementExtensions) {
    copy(Element.Methods, HTMLElement.prototype);
    copy(Element.Methods.Simulated, HTMLElement.prototype, true);
  }

  if (F.SpecificElementExtensions) {
    for (var tag in Element.Methods.ByTag) {
      var klass = findDOMClass(tag);
      if (Object.isUndefined(klass)) continue;
      copy(T[tag], klass.prototype);
    }
  }

  Object.extend(Element, Element.Methods);
  delete Element.ByTag;

  if (Element.extend.refresh) Element.extend.refresh();
  Element.cache = { };
};

document.viewport = {
  getDimensions: function() {
    var dimensions = { };
    var B = Prototype.Browser;
    $w('width height').each(function(d) {
      var D = d.capitalize();
      dimensions[d] = (B.WebKit && !document.evaluate) ? self['inner' + D] :
        (B.Opera) ? document.body['client' + D] : document.documentElement['client' + D];
    });
    return dimensions;
  },

  getWidth: function() {
    return this.getDimensions().width;
  },

  getHeight: function() {
    return this.getDimensions().height;
  },

  getScrollOffsets: function() {
    return Element._returnOffset(
      window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
      window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
  }
};
/* Portions of the Selector class are derived from Jack Slocum’s DomQuery,
 * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
 * license.  Please see http://www.yui-ext.com/ for more information. */

var Selector = Class.create({
  initialize: function(expression) {
    this.expression = expression.strip();
    this.compileMatcher();
  },

  shouldUseXPath: function() {
    if (!Prototype.BrowserFeatures.XPath) return false;

    var e = this.expression;

    // Safari 3 chokes on :*-of-type and :empty
    if (Prototype.Browser.WebKit &&
     (e.include("-of-type") || e.include(":empty")))
      return false;

    // XPath can't do namespaced attributes, nor can it read
    // the "checked" property from DOM nodes
    if ((/(\[[\w-]*?:|:checked)/).test(this.expression))
      return false;

    return true;
  },

  compileMatcher: function() {
    if (this.shouldUseXPath())
      return this.compileXPathMatcher();

    var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
        c = Selector.criteria, le, p, m;

    if (Selector._cache[e]) {
      this.matcher = Selector._cache[e];
      return;
    }

    this.matcher = ["this.matcher = function(root) {",
                    "var r = root, h = Selector.handlers, c = false, n;"];

    while (e && le != e && (/\S/).test(e)) {
      le = e;
      for (var i in ps) {
        p = ps[i];
        if (m = e.match(p)) {
          this.matcher.push(Object.isFunction(c[i]) ? c[i](m) :
    	      new Template(c[i]).evaluate(m));
          e = e.replace(m[0], '');
          break;
        }
      }
    }

    this.matcher.push("return h.unique(n);\n}");
    eval(this.matcher.join('\n'));
    Selector._cache[this.expression] = this.matcher;
  },

  compileXPathMatcher: function() {
    var e = this.expression, ps = Selector.patterns,
        x = Selector.xpath, le, m;

    if (Selector._cache[e]) {
      this.xpath = Selector._cache[e]; return;
    }

    this.matcher = ['.//*'];
    while (e && le != e && (/\S/).test(e)) {
      le = e;
      for (var i in ps) {
        if (m = e.match(ps[i])) {
          this.matcher.push(Object.isFunction(x[i]) ? x[i](m) :
            new Template(x[i]).evaluate(m));
          e = e.replace(m[0], '');
          break;
        }
      }
    }

    this.xpath = this.matcher.join('');
    Selector._cache[this.expression] = this.xpath;
  },

  findElements: function(root) {
    root = root || document;
    if (this.xpath) return document._getElementsByXPath(this.xpath, root);
    return this.matcher(root);
  },

  match: function(element) {
    this.tokens = [];

    var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
    var le, p, m;

    while (e && le !== e && (/\S/).test(e)) {
      le = e;
      for (var i in ps) {
        p = ps[i];
        if (m = e.match(p)) {
          // use the Selector.assertions methods unless the selector
          // is too complex.
          if (as[i]) {
            this.tokens.push([i, Object.clone(m)]);
            e = e.replace(m[0], '');
          } else {
            // reluctantly do a document-wide search
            // and look for a match in the array
            return this.findElements(document).include(element);
          }
        }
      }
    }

    var match = true, name, matches;
    for (var i = 0, token; token = this.tokens[i]; i++) {
      name = token[0], matches = token[1];
      if (!Selector.assertions[name](element, matches)) {
        match = false; break;
      }
    }

    return match;
  },

  toString: function() {
    return this.expression;
  },

  inspect: function() {
    return "#<Selector:" + this.expression.inspect() + ">";
  }
});

Object.extend(Selector, {
  _cache: { },

  xpath: {
    descendant:   "//*",
    child:        "/*",
    adjacent:     "/following-sibling::*[1]",
    laterSibling: '/following-sibling::*',
    tagName:      function(m) {
      if (m[1] == '*') return '';
      return "[local-name()='" + m[1].toLowerCase() +
             "' or local-name()='" + m[1].toUpperCase() + "']";
    },
    className:    "[contains(concat(' ', @class, ' '), ' #{1} ')]",
    id:           "[@id='#{1}']",
    attrPresence: function(m) {
      m[1] = m[1].toLowerCase();
      return new Template("[@#{1}]").evaluate(m);
    },
    attr: function(m) {
      m[1] = m[1].toLowerCase();
      m[3] = m[5] || m[6];
      return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
    },
    pseudo: function(m) {
      var h = Selector.xpath.pseudos[m[1]];
      if (!h) return '';
      if (Object.isFunction(h)) return h(m);
      return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
    },
    operators: {
      '=':  "[@#{1}='#{3}']",
      '!=': "[@#{1}!='#{3}']",
      '^=': "[starts-with(@#{1}, '#{3}')]",
      '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
      '*=': "[contains(@#{1}, '#{3}')]",
      '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
      '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
    },
    pseudos: {
      'first-child': '[not(preceding-sibling::*)]',
      'last-child':  '[not(following-sibling::*)]',
      'only-child':  '[not(preceding-sibling::* or following-sibling::*)]',
      'empty':       "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]",
      'checked':     "[@checked]",
      'disabled':    "[@disabled]",
      'enabled':     "[not(@disabled)]",
      'not': function(m) {
        var e = m[6], p = Selector.patterns,
            x = Selector.xpath, le, v;

        var exclusion = [];
        while (e && le != e && (/\S/).test(e)) {
          le = e;
          for (var i in p) {
            if (m = e.match(p[i])) {
              v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m);
              exclusion.push("(" + v.substring(1, v.length - 1) + ")");
              e = e.replace(m[0], '');
              break;
            }
          }
        }
        return "[not(" + exclusion.join(" and ") + ")]";
      },
      'nth-child':      function(m) {
        return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
      },
      'nth-last-child': function(m) {
        return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
      },
      'nth-of-type':    function(m) {
        return Selector.xpath.pseudos.nth("position() ", m);
      },
      'nth-last-of-type': function(m) {
        return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
      },
      'first-of-type':  function(m) {
        m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
      },
      'last-of-type':   function(m) {
        m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
      },
      'only-of-type':   function(m) {
        var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
      },
      nth: function(fragment, m) {
        var mm, formula = m[6], predicate;
        if (formula == 'even') formula = '2n+0';
        if (formula == 'odd')  formula = '2n+1';
        if (mm = formula.match(/^(\d+)$/)) // digit only
          return '[' + fragment + "= " + mm[1] + ']';
        if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
          if (mm[1] == "-") mm[1] = -1;
          var a = mm[1] ? Number(mm[1]) : 1;
          var b = mm[2] ? Number(mm[2]) : 0;
          predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
          "((#{fragment} - #{b}) div #{a} >= 0)]";
          return new Template(predicate).evaluate({
            fragment: fragment, a: a, b: b });
        }
      }
    }
  },

  criteria: {
    tagName:      'n = h.tagName(n, r, "#{1}", c);      c = false;',
    className:    'n = h.className(n, r, "#{1}", c);    c = false;',
    id:           'n = h.id(n, r, "#{1}", c);           c = false;',
    attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;',
    attr: function(m) {
      m[3] = (m[5] || m[6]);
      return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m);
    },
    pseudo: function(m) {
      if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
      return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
    },
    descendant:   'c = "descendant";',
    child:        'c = "child";',
    adjacent:     'c = "adjacent";',
    laterSibling: 'c = "laterSibling";'
  },

  patterns: {
    // combinators must be listed first
    // (and descendant needs to be last combinator)
    laterSibling: /^\s*~\s*/,
    child:        /^\s*>\s*/,
    adjacent:     /^\s*\+\s*/,
    descendant:   /^\s/,

    // selectors follow
    tagName:      /^\s*(\*|[\w\-]+)(\b|$)?/,
    id:           /^#([\w\-\*]+)(\b|$)/,
    className:    /^\.([\w\-\*]+)(\b|$)/,
    pseudo:
/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,
    attrPresence: /^\[([\w]+)\]/,
    attr:         /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
  },

  // for Selector.match and Element#match
  assertions: {
    tagName: function(element, matches) {
      return matches[1].toUpperCase() == element.tagName.toUpperCase();
    },

    className: function(element, matches) {
      return Element.hasClassName(element, matches[1]);
    },

    id: function(element, matches) {
      return element.id === matches[1];
    },

    attrPresence: function(element, matches) {
      return Element.hasAttribute(element, matches[1]);
    },

    attr: function(element, matches) {
      var nodeValue = Element.readAttribute(element, matches[1]);
      return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);
    }
  },

  handlers: {
    // UTILITY FUNCTIONS
    // joins two collections
    concat: function(a, b) {
      for (var i = 0, node; node = b[i]; i++)
        a.push(node);
      return a;
    },

    // marks an array of nodes for counting
    mark: function(nodes) {
      var _true = Prototype.emptyFunction;
      for (var i = 0, node; node = nodes[i]; i++)
        node._countedByPrototype = _true;
      return nodes;
    },

    unmark: function(nodes) {
      for (var i = 0, node; node = nodes[i]; i++)
        node._countedByPrototype = undefined;
      return nodes;
    },

    // mark each child node with its position (for nth calls)
    // "ofType" flag indicates whether we're indexing for nth-of-type
    // rather than nth-child
    index: function(parentNode, reverse, ofType) {
      parentNode._countedByPrototype = Prototype.emptyFunction;
      if (reverse) {
        for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
          var node = nodes[i];
          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
        }
      } else {
        for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
      }
    },

    // filters out duplicates and extends all nodes
    unique: function(nodes) {
      if (nodes.length == 0) return nodes;
      var results = [], n;
      for (var i = 0, l = nodes.length; i < l; i++)
        if (!(n = nodes[i])._countedByPrototype) {
          n._countedByPrototype = Prototype.emptyFunction;
          results.push(Element.extend(n));
        }
      return Selector.handlers.unmark(results);
    },

    // COMBINATOR FUNCTIONS
    descendant: function(nodes) {
      var h = Selector.handlers;
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        h.concat(results, node.getElementsByTagName('*'));
      return results;
    },

    child: function(nodes) {
      var h = Selector.handlers;
      for (var i = 0, results = [], node; node = nodes[i]; i++) {
        for (var j = 0, child; child = node.childNodes[j]; j++)
          if (child.nodeType == 1 && child.tagName != '!') results.push(child);
      }
      return results;
    },

    adjacent: function(nodes) {
      for (var i = 0, results = [], node; node = nodes[i]; i++) {
        var next = this.nextElementSibling(node);
        if (next) results.push(next);
      }
      return results;
    },

    laterSibling: function(nodes) {
      var h = Selector.handlers;
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        h.concat(results, Element.nextSiblings(node));
      return results;
    },

    nextElementSibling: function(node) {
      while (node = node.nextSibling)
	      if (node.nodeType == 1) return node;
      return null;
    },

    previousElementSibling: function(node) {
      while (node = node.previousSibling)
        if (node.nodeType == 1) return node;
      return null;
    },

    // TOKEN FUNCTIONS
    tagName: function(nodes, root, tagName, combinator) {
      var uTagName = tagName.toUpperCase();
      var results = [], h = Selector.handlers;
      if (nodes) {
        if (combinator) {
          // fastlane for ordinary descendant combinators
          if (combinator == "descendant") {
            for (var i = 0, node; node = nodes[i]; i++)
              h.concat(results, node.getElementsByTagName(tagName));
            return results;
          } else nodes = this[combinator](nodes);
          if (tagName == "*") return nodes;
        }
        for (var i = 0, node; node = nodes[i]; i++)
          if (node.tagName.toUpperCase() === uTagName) results.push(node);
        return results;
      } else return root.getElementsByTagName(tagName);
    },

    id: function(nodes, root, id, combinator) {
      var targetNode = $(id), h = Selector.handlers;
      if (!targetNode) return [];
      if (!nodes && root == document) return [targetNode];
      if (nodes) {
        if (combinator) {
          if (combinator == 'child') {
            for (var i = 0, node; node = nodes[i]; i++)
              if (targetNode.parentNode == node) return [targetNode];
          } else if (combinator == 'descendant') {
            for (var i = 0, node; node = nodes[i]; i++)
              if (Element.descendantOf(targetNode, node)) return [targetNode];
          } else if (combinator == 'adjacent') {
            for (var i = 0, node; node = nodes[i]; i++)
              if (Selector.handlers.previousElementSibling(targetNode) == node)
                return [targetNode];
          } else nodes = h[combinator](nodes);
        }
        for (var i = 0, node; node = nodes[i]; i++)
          if (node == targetNode) return [targetNode];
        return [];
      }
      return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
    },

    className: function(nodes, root, className, combinator) {
      if (nodes && combinator) nodes = this[combinator](nodes);
      return Selector.handlers.byClassName(nodes, root, className);
    },

    byClassName: function(nodes, root, className) {
      if (!nodes) nodes = Selector.handlers.descendant([root]);
      var needle = ' ' + className + ' ';
      for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
        nodeClassName = node.className;
        if (nodeClassName.length == 0) continue;
        if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
          results.push(node);
      }
      return results;
    },

    attrPresence: function(nodes, root, attr, combinator) {
      if (!nodes) nodes = root.getElementsByTagName("*");
      if (nodes && combinator) nodes = this[combinator](nodes);
      var results = [];
      for (var i = 0, node; node = nodes[i]; i++)
        if (Element.hasAttribute(node, attr)) results.push(node);
      return results;
    },

    attr: function(nodes, root, attr, value, operator, combinator) {
      if (!nodes) nodes = root.getElementsByTagName("*");
      if (nodes && combinator) nodes = this[combinator](nodes);
      var handler = Selector.operators[operator], results = [];
      for (var i = 0, node; node = nodes[i]; i++) {
        var nodeValue = Element.readAttribute(node, attr);
        if (nodeValue === null) continue;
        if (handler(nodeValue, value)) results.push(node);
      }
      return results;
    },

    pseudo: function(nodes, name, value, root, combinator) {
      if (nodes && combinator) nodes = this[combinator](nodes);
      if (!nodes) nodes = root.getElementsByTagName("*");
      return Selector.pseudos[name](nodes, value, root);
    }
  },

  pseudos: {
    'first-child': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++) {
        if (Selector.handlers.previousElementSibling(node)) continue;
          results.push(node);
      }
      return results;
    },
    'last-child': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++) {
        if (Selector.handlers.nextElementSibling(node)) continue;
          results.push(node);
      }
      return results;
    },
    'only-child': function(nodes, value, root) {
      var h = Selector.handlers;
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
          results.push(node);
      return results;
    },
    'nth-child':        function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, formula, root);
    },
    'nth-last-child':   function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, formula, root, true);
    },
    'nth-of-type':      function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, formula, root, false, true);
    },
    'nth-last-of-type': function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, formula, root, true, true);
    },
    'first-of-type':    function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, "1", root, false, true);
    },
    'last-of-type':     function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, "1", root, true, true);
    },
    'only-of-type':     function(nodes, formula, root) {
      var p = Selector.pseudos;
      return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
    },

    // handles the an+b logic
    getIndices: function(a, b, total) {
      if (a == 0) return b > 0 ? [b] : [];
      return $R(1, total).inject([], function(memo, i) {
        if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
        return memo;
      });
    },

    // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type
    nth: function(nodes, formula, root, reverse, ofType) {
      if (nodes.length == 0) return [];
      if (formula == 'even') formula = '2n+0';
      if (formula == 'odd')  formula = '2n+1';
      var h = Selector.handlers, results = [], indexed = [], m;
      h.mark(nodes);
      for (var i = 0, node; node = nodes[i]; i++) {
        if (!node.parentNode._countedByPrototype) {
          h.index(node.parentNode, reverse, ofType);
          indexed.push(node.parentNode);
        }
      }
      if (formula.match(/^\d+$/)) { // just a number
        formula = Number(formula);
        for (var i = 0, node; node = nodes[i]; i++)
          if (node.nodeIndex == formula) results.push(node);
      } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
        if (m[1] == "-") m[1] = -1;
        var a = m[1] ? Number(m[1]) : 1;
        var b = m[2] ? Number(m[2]) : 0;
        var indices = Selector.pseudos.getIndices(a, b, nodes.length);
        for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
          for (var j = 0; j < l; j++)
            if (node.nodeIndex == indices[j]) results.push(node);
        }
      }
      h.unmark(nodes);
      h.unmark(indexed);
      return results;
    },

    'empty': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++) {
        // IE treats comments as element nodes
        if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue;
        results.push(node);
      }
      return results;
    },

    'not': function(nodes, selector, root) {
      var h = Selector.handlers, selectorType, m;
      var exclusions = new Selector(selector).findElements(root);
      h.mark(exclusions);
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        if (!node._countedByPrototype) results.push(node);
      h.unmark(exclusions);
      return results;
    },

    'enabled': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        if (!node.disabled) results.push(node);
      return results;
    },

    'disabled': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        if (node.disabled) results.push(node);
      return results;
    },

    'checked': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        if (node.checked) results.push(node);
      return results;
    }
  },

  operators: {
    '=':  function(nv, v) { return nv == v; },
    '!=': function(nv, v) { return nv != v; },
    '^=': function(nv, v) { return nv.startsWith(v); },
    '$=': function(nv, v) { return nv.endsWith(v); },
    '*=': function(nv, v) { return nv.include(v); },
    '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
    '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); }
  },

  split: function(expression) {
    var expressions = [];
    expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
      expressions.push(m[1].strip());
    });
    return expressions;
  },

  matchElements: function(elements, expression) {
    var matches = $$(expression), h = Selector.handlers;
    h.mark(matches);
    for (var i = 0, results = [], element; element = elements[i]; i++)
      if (element._countedByPrototype) results.push(element);
    h.unmark(matches);
    return results;
  },

  findElement: function(elements, expression, index) {
    if (Object.isNumber(expression)) {
      index = expression; expression = false;
    }
    return Selector.matchElements(elements, expression || '*')[index || 0];
  },

  findChildElements: function(element, expressions) {
    expressions = Selector.split(expressions.join(','));
    var results = [], h = Selector.handlers;
    for (var i = 0, l = expressions.length, selector; i < l; i++) {
      selector = new Selector(expressions[i].strip());
      h.concat(results, selector.findElements(element));
    }
    return (l > 1) ? h.unique(results) : results;
  }
});

if (Prototype.Browser.IE) {
  Object.extend(Selector.handlers, {
    // IE returns comment nodes on getElementsByTagName("*").
    // Filter them out.
    concat: function(a, b) {
      for (var i = 0, node; node = b[i]; i++)
        if (node.tagName !== "!") a.push(node);
      return a;
    },

    // IE improperly serializes _countedByPrototype in (inner|outer)HTML.
    unmark: function(nodes) {
      for (var i = 0, node; node = nodes[i]; i++)
        node.removeAttribute('_countedByPrototype');
      return nodes;
    }
  });
}

function $$() {
  return Selector.findChildElements(document, $A(arguments));
}
var Form = {
  reset: function(form) {
    $(form).reset();
    return form;
  },

  serializeElements: function(elements, options) {
    if (typeof options != 'object') options = { hash: !!options };
    else if (Object.isUndefined(options.hash)) options.hash = true;
    var key, value, submitted = false, submit = options.submit;

    var data = elements.inject({ }, function(result, element) {
      if (!element.disabled && element.name) {
        key = element.name; value = $(element).getValue();
        if (value != null && (element.type != 'submit' || (!submitted &&
            submit !== false && (!submit || key == submit) && (submitted = true)))) {
          if (key in result) {
            // a key is already present; construct an array of values
            if (!Object.isArray(result[key])) result[key] = [result[key]];
            result[key].push(value);
          }
          else result[key] = value;
        }
      }
      return result;
    });

    return options.hash ? data : Object.toQueryString(data);
  }
};

Form.Methods = {
  serialize: function(form, options) {
    return Form.serializeElements(Form.getElements(form), options);
  },

  getElements: function(form) {
    return $A($(form).getElementsByTagName('*')).inject([],
      function(elements, child) {
        if (Form.Element.Serializers[child.tagName.toLowerCase()])
          elements.push(Element.extend(child));
        return elements;
      }
    );
  },

  getInputs: function(form, typeName, name) {
    form = $(form);
    var inputs = form.getElementsByTagName('input');

    if (!typeName && !name) return $A(inputs).map(Element.extend);

    for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
      var input = inputs[i];
      if ((typeName && input.type != typeName) || (name && input.name != name))
        continue;
      matchingInputs.push(Element.extend(input));
    }

    return matchingInputs;
  },

  disable: function(form) {
    form = $(form);
    Form.getElements(form).invoke('disable');
    return form;
  },

  enable: function(form) {
    form = $(form);
    Form.getElements(form).invoke('enable');
    return form;
  },

  findFirstElement: function(form) {
    var elements = $(form).getElements().findAll(function(element) {
      return 'hidden' != element.type && !element.disabled;
    });
    var firstByIndex = elements.findAll(function(element) {
      return element.hasAttribute('tabIndex') && element.tabIndex >= 0;
    }).sortBy(function(element) { return element.tabIndex }).first();

    return firstByIndex ? firstByIndex : elements.find(function(element) {
      return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
    });
  },

  focusFirstElement: function(form) {
    form = $(form);
    form.findFirstElement().activate();
    return form;
  },

  request: function(form, options) {
    form = $(form), options = Object.clone(options || { });

    var params = options.parameters, action = form.readAttribute('action') || '';
    if (action.blank()) action = window.location.href;
    options.parameters = form.serialize(true);

    if (params) {
      if (Object.isString(params)) params = params.toQueryParams();
      Object.extend(options.parameters, params);
    }

    if (form.hasAttribute('method') && !options.method)
      options.method = form.method;

    return new Ajax.Request(action, options);
  }
};

/*--------------------------------------------------------------------------*/

Form.Element = {
  focus: function(element) {
    $(element).focus();
    return element;
  },

  select: function(element) {
    $(element).select();
    return element;
  }
};

Form.Element.Methods = {
  serialize: function(element) {
    element = $(element);
    if (!element.disabled && element.name) {
      var value = element.getValue();
      if (value != undefined) {
        var pair = { };
        pair[element.name] = value;
        return Object.toQueryString(pair);
      }
    }
    return '';
  },

  getValue: function(element) {
    element = $(element);
    var method = element.tagName.toLowerCase();
    return Form.Element.Serializers[method](element);
  },

  setValue: function(element, value) {
    element = $(element);
    var method = element.tagName.toLowerCase();
    Form.Element.Serializers[method](element, value);
    return element;
  },

  clear: function(element) {
    $(element).value = '';
    return element;
  },

  present: function(element) {
    return $(element).value != '';
  },

  activate: function(element) {
    element = $(element);
    try {
      element.focus();
      if (element.select && (element.tagName.toLowerCase() != 'input' ||
          !['button', 'reset', 'submit'].include(element.type)))
        element.select();
    } catch (e) { }
    return element;
  },

  disable: function(element) {
    element = $(element);
    element.blur();
    element.disabled = true;
    return element;
  },

  enable: function(element) {
    element = $(element);
    element.disabled = false;
    return element;
  }
};

/*--------------------------------------------------------------------------*/

var Field = Form.Element;
var $F = Form.Element.Methods.getValue;

/*--------------------------------------------------------------------------*/

Form.Element.Serializers = {
  input: function(element, value) {
    switch (element.type.toLowerCase()) {
      case 'checkbox':
      case 'radio':
        return Form.Element.Serializers.inputSelector(element, value);
      default:
        return Form.Element.Serializers.textarea(element, value);
    }
  },

  inputSelector: function(element, value) {
    if (Object.isUndefined(value)) return element.checked ? element.value : null;
    else element.checked = !!value;
  },

  textarea: function(element, value) {
    if (Object.isUndefined(value)) return element.value;
    else element.value = value;
  },

  select: function(element, index) {
    if (Object.isUndefined(index))
      return this[element.type == 'select-one' ?
        'selectOne' : 'selectMany'](element);
    else {
      var opt, value, single = !Object.isArray(index);
      for (var i = 0, length = element.length; i < length; i++) {
        opt = element.options[i];
        value = this.optionValue(opt);
        if (single) {
          if (value == index) {
            opt.selected = true;
            return;
          }
        }
        else opt.selected = index.include(value);
      }
    }
  },

  selectOne: function(element) {
    var index = element.selectedIndex;
    return index >= 0 ? this.optionValue(element.options[index]) : null;
  },

  selectMany: function(element) {
    var values, length = element.length;
    if (!length) return null;

    for (var i = 0, values = []; i < length; i++) {
      var opt = element.options[i];
      if (opt.selected) values.push(this.optionValue(opt));
    }
    return values;
  },

  optionValue: function(opt) {
    // extend element because hasAttribute may not be native
    return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
  }
};

/*--------------------------------------------------------------------------*/

Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
  initialize: function($super, element, frequency, callback) {
    $super(callback, frequency);
    this.element   = $(element);
    this.lastValue = this.getValue();
  },

  execute: function() {
    var value = this.getValue();
    if (Object.isString(this.lastValue) && Object.isString(value) ?
        this.lastValue != value : String(this.lastValue) != String(value)) {
      this.callback(this.element, value);
      this.lastValue = value;
    }
  }
});

Form.Element.Observer = Class.create(Abstract.TimedObserver, {
  getValue: function() {
    return Form.Element.getValue(this.element);
  }
});

Form.Observer = Class.create(Abstract.TimedObserver, {
  getValue: function() {
    return Form.serialize(this.element);
  }
});

/*--------------------------------------------------------------------------*/

Abstract.EventObserver = Class.create({
  initialize: function(element, callback) {
    this.element  = $(element);
    this.callback = callback;

    this.lastValue = this.getValue();
    if (this.element.tagName.toLowerCase() == 'form')
      this.registerFormCallbacks();
    else
      this.registerCallback(this.element);
  },

  onElementEvent: function() {
    var value = this.getValue();
    if (this.lastValue != value) {
      this.callback(this.element, value);
      this.lastValue = value;
    }
  },

  registerFormCallbacks: function() {
    Form.getElements(this.element).each(this.registerCallback, this);
  },

  registerCallback: function(element) {
    if (element.type) {
      switch (element.type.toLowerCase()) {
        case 'checkbox':
        case 'radio':
          Event.observe(element, 'click', this.onElementEvent.bind(this));
          break;
        default:
          Event.observe(element, 'change', this.onElementEvent.bind(this));
          break;
      }
    }
  }
});

Form.Element.EventObserver = Class.create(Abstract.EventObserver, {
  getValue: function() {
    return Form.Element.getValue(this.element);
  }
});

Form.EventObserver = Class.create(Abstract.EventObserver, {
  getValue: function() {
    return Form.serialize(this.element);
  }
});
if (!window.Event) var Event = { };

Object.extend(Event, {
  KEY_BACKSPACE: 8,
  KEY_TAB:       9,
  KEY_RETURN:   13,
  KEY_ESC:      27,
  KEY_LEFT:     37,
  KEY_UP:       38,
  KEY_RIGHT:    39,
  KEY_DOWN:     40,
  KEY_DELETE:   46,
  KEY_HOME:     36,
  KEY_END:      35,
  KEY_PAGEUP:   33,
  KEY_PAGEDOWN: 34,
  KEY_INSERT:   45,

  cache: { },

  relatedTarget: function(event) {
    var element;
    switch(event.type) {
      case 'mouseover': element = event.fromElement; break;
      case 'mouseout':  element = event.toElement;   break;
      default: return null;
    }
    return Element.extend(element);
  }
});

Event.Methods = (function() {
  var isButton;

  if (Prototype.Browser.IE) {
    var buttonMap = { 0: 1, 1: 4, 2: 2 };
    isButton = function(event, code) {
      return event.button == buttonMap[code];
    };

  } else if (Prototype.Browser.WebKit) {
    isButton = function(event, code) {
      switch (code) {
        case 0: return event.which == 1 && !event.metaKey;
        case 1: return event.which == 1 && event.metaKey;
        default: return false;
      }
    };

  } else {
    isButton = function(event, code) {
      return event.which ? (event.which === code + 1) : (event.button === code);
    };
  }

  return {
    isLeftClick:   function(event) { return isButton(event, 0) },
    isMiddleClick: function(event) { return isButton(event, 1) },
    isRightClick:  function(event) { return isButton(event, 2) },

    element: function(event) {
      var node = Event.extend(event).target;
      return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node);
    },

    findElement: function(event, expression) {
      var element = Event.element(event);
      if (!expression) return element;
      var elements = [element].concat(element.ancestors());
      return Selector.findElement(elements, expression, 0);
    },

    pointer: function(event) {
      return {
        x: event.pageX || (event.clientX +
          (document.documentElement.scrollLeft || document.body.scrollLeft)),
        y: event.pageY || (event.clientY +
          (document.documentElement.scrollTop || document.body.scrollTop))
      };
    },

    pointerX: function(event) { return Event.pointer(event).x },
    pointerY: function(event) { return Event.pointer(event).y },

    stop: function(event) {
      Event.extend(event);
      event.preventDefault();
      event.stopPropagation();
      event.stopped = true;
    }
  };
})();

Event.extend = (function() {
  var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
    m[name] = Event.Methods[name].methodize();
    return m;
  });

  if (Prototype.Browser.IE) {
    Object.extend(methods, {
      stopPropagation: function() { this.cancelBubble = true },
      preventDefault:  function() { this.returnValue = false },
      inspect: function() { return "[object Event]" }
    });

    return function(event) {
      if (!event) return false;
      if (event._extendedByPrototype) return event;

      event._extendedByPrototype = Prototype.emptyFunction;
      var pointer = Event.pointer(event);
      Object.extend(event, {
        target: event.srcElement,
        relatedTarget: Event.relatedTarget(event),
        pageX:  pointer.x,
        pageY:  pointer.y
      });
      return Object.extend(event, methods);
    };

  } else {
    Event.prototype = Event.prototype || document.createEvent("HTMLEvents").__proto__;
    Object.extend(Event.prototype, methods);
    return Prototype.K;
  }
})();

Object.extend(Event, (function() {
  var cache = Event.cache;

  function getEventID(element) {
    if (element._prototypeEventID) return element._prototypeEventID[0];
    arguments.callee.id = arguments.callee.id || 1;
    return element._prototypeEventID = [++arguments.callee.id];
  }

  function getDOMEventName(eventName) {
    if (eventName && eventName.include(':')) return "dataavailable";
    return eventName;
  }

  function getCacheForID(id) {
    return cache[id] = cache[id] || { };
  }

  function getWrappersForEventName(id, eventName) {
    var c = getCacheForID(id);
    return c[eventName] = c[eventName] || [];
  }

  function createWrapper(element, eventName, handler) {
    var id = getEventID(element);
    var c = getWrappersForEventName(id, eventName);
    if (c.pluck("handler").include(handler)) return false;

    var wrapper = function(event) {
      if (!Event || !Event.extend ||
        (event.eventName && event.eventName != eventName))
          return false;

      Event.extend(event);
      handler.call(element, event);
    };

    wrapper.handler = handler;
    c.push(wrapper);
    return wrapper;
  }

  function findWrapper(id, eventName, handler) {
    var c = getWrappersForEventName(id, eventName);
    return c.find(function(wrapper) { return wrapper.handler == handler });
  }

  function destroyWrapper(id, eventName, handler) {
    var c = getCacheForID(id);
    if (!c[eventName]) return false;
    c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));
  }

  function destroyCache() {
    for (var id in cache)
      for (var eventName in cache[id])
        cache[id][eventName] = null;
  }

  if (window.attachEvent) {
    window.attachEvent("onunload", destroyCache);
  }

  return {
    observe: function(element, eventName, handler) {
      element = $(element);
      var name = getDOMEventName(eventName);

      var wrapper = createWrapper(element, eventName, handler);
      if (!wrapper) return element;

      if (element.addEventListener) {
        element.addEventListener(name, wrapper, false);
      } else {
        element.attachEvent("on" + name, wrapper);
      }

      return element;
    },

    stopObserving: function(element, eventName, handler) {
      element = $(element);
      var id = getEventID(element), name = getDOMEventName(eventName);

      if (!handler && eventName) {
        getWrappersForEventName(id, eventName).each(function(wrapper) {
            if(element && element.stopObserving){                               // this line added by Jive Software
                element.stopObserving(eventName, wrapper.handler);
            }                                                                   // this line added by Jive Software
        });
        return element;

      } else if (!eventName) {
        Object.keys(getCacheForID(id)).each(function(eventName) {
          element.stopObserving(eventName);
        });
        return element;
      }

      var wrapper = findWrapper(id, eventName, handler);
      if (!wrapper) return element;

      if (element.removeEventListener) {
        element.removeEventListener(name, wrapper, false);
      } else {
        element.detachEvent("on" + name, wrapper);
      }

      destroyWrapper(id, eventName, handler);

      return element;
    },

    fire: function(element, eventName, memo) {
      element = $(element);
      if (element == document && document.createEvent && !element.dispatchEvent)
        element = document.documentElement;

      var event;
      if (document.createEvent) {
        event = document.createEvent("HTMLEvents");
        event.initEvent("dataavailable", true, true);
      } else {
        event = document.createEventObject();
        event.eventType = "ondataavailable";
      }

      event.eventName = eventName;
      event.memo = memo || { };

      if (document.createEvent) {
        element.dispatchEvent(event);
      } else {
        element.fireEvent(event.eventType, event);
      }

      return Event.extend(event);
    }
  };
})());

Object.extend(Event, Event.Methods);

Element.addMethods({
  fire:          Event.fire,
  observe:       Event.observe,
  stopObserving: Event.stopObserving
});

Object.extend(document, {
  fire:          Element.Methods.fire.methodize(),
  observe:       Element.Methods.observe.methodize(),
  stopObserving: Element.Methods.stopObserving.methodize(),
  loaded:        false
});

(function() {
  /* Support for the DOMContentLoaded event is based on work by Dan Webb,
     Matthias Miller, Dean Edwards and John Resig. */

  var timer;

  function fireContentLoadedEvent() {
    if (document.loaded) return;
    if (timer) window.clearInterval(timer);
    document.fire("dom:loaded");
    document.loaded = true;
  }

  if (document.addEventListener) {
    if (Prototype.Browser.WebKit) {
      timer = window.setInterval(function() {
        if (/loaded|complete/.test(document.readyState))
          fireContentLoadedEvent();
      }, 0);

      Event.observe(window, "load", fireContentLoadedEvent);

    } else {
      document.addEventListener("DOMContentLoaded",
        fireContentLoadedEvent, false);
    }

  } else {
    document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");
    $("__onDOMContentLoaded").onreadystatechange = function() {
      if (this.readyState == "complete") {
        this.onreadystatechange = null;
        fireContentLoadedEvent();
      }
    };
  }
})();
/*------------------------------- DEPRECATED -------------------------------*/

Hash.toQueryString = Object.toQueryString;

var Toggle = { display: Element.toggle };

Element.Methods.childOf = Element.Methods.descendantOf;

var Insertion = {
  Before: function(element, content) {
    return Element.insert(element, {before:content});
  },

  Top: function(element, content) {
    return Element.insert(element, {top:content});
  },

  Bottom: function(element, content) {
    return Element.insert(element, {bottom:content});
  },

  After: function(element, content) {
    return Element.insert(element, {after:content});
  }
};

var $continue = new Error('"throw $continue" is deprecated, use "return" instead');

// This should be moved to script.aculo.us; notice the deprecated methods
// further below, that map to the newer Element methods.
var Position = {
  // set to true if needed, warning: firefox performance problems
  // NOT neeeded for page scrolling, only if draggable contained in
  // scrollable elements
  includeScrollOffsets: false,

  // must be called before calling withinIncludingScrolloffset, every time the
  // page is scrolled
  prepare: function() {
    this.deltaX =  window.pageXOffset
                || document.documentElement.scrollLeft
                || document.body.scrollLeft
                || 0;
    this.deltaY =  window.pageYOffset
                || document.documentElement.scrollTop
                || document.body.scrollTop
                || 0;
  },

  // caches x/y coordinate pair to use with overlap
  within: function(element, x, y) {
    if (this.includeScrollOffsets)
      return this.withinIncludingScrolloffsets(element, x, y);
    this.xcomp = x;
    this.ycomp = y;
    this.offset = Element.cumulativeOffset(element);

    return (y >= this.offset[1] &&
            y <  this.offset[1] + element.offsetHeight &&
            x >= this.offset[0] &&
            x <  this.offset[0] + element.offsetWidth);
  },

  withinIncludingScrolloffsets: function(element, x, y) {
    var offsetcache = Element.cumulativeScrollOffset(element);

    this.xcomp = x + offsetcache[0] - this.deltaX;
    this.ycomp = y + offsetcache[1] - this.deltaY;
    this.offset = Element.cumulativeOffset(element);

    return (this.ycomp >= this.offset[1] &&
            this.ycomp <  this.offset[1] + element.offsetHeight &&
            this.xcomp >= this.offset[0] &&
            this.xcomp <  this.offset[0] + element.offsetWidth);
  },

  // within must be called directly before
  overlap: function(mode, element) {
    if (!mode) return 0;
    if (mode == 'vertical')
      return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
        element.offsetHeight;
    if (mode == 'horizontal')
      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
        element.offsetWidth;
  },

  // Deprecation layer -- use newer Element methods now (1.5.2).

  cumulativeOffset: Element.Methods.cumulativeOffset,

  positionedOffset: Element.Methods.positionedOffset,

  absolutize: function(element) {
    Position.prepare();
    return Element.absolutize(element);
  },

  relativize: function(element) {
    Position.prepare();
    return Element.relativize(element);
  },

  realOffset: Element.Methods.cumulativeScrollOffset,

  offsetParent: Element.Methods.getOffsetParent,

  page: Element.Methods.viewportOffset,

  clone: function(source, target, options) {
    options = options || { };
    return Element.clonePosition(target, source, options);
  }
};

/*--------------------------------------------------------------------------*/

if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){
  function iter(name) {
    return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";
  }

  instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?
  function(element, className) {
    className = className.toString().strip();
    var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);
    return cond ? document._getElementsByXPath('.//*' + cond, element) : [];
  } : function(element, className) {
    className = className.toString().strip();
    var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);
    if (!classNames && !className) return elements;

    var nodes = $(element).getElementsByTagName('*');
    className = ' ' + className + ' ';

    for (var i = 0, child, cn; child = nodes[i]; i++) {
      if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||
          (classNames && classNames.all(function(name) {
            return !name.toString().blank() && cn.include(' ' + name + ' ');
          }))))
        elements.push(Element.extend(child));
    }
    return elements;
  };

  return function(className, parentElement) {
    return $(parentElement || document.body).getElementsByClassName(className);
  };
}(Element.Methods);

/*--------------------------------------------------------------------------*/

Element.ClassNames = Class.create();
Element.ClassNames.prototype = {
  initialize: function(element) {
    this.element = $(element);
  },

  _each: function(iterator) {
    this.element.className.split(/\s+/).select(function(name) {
      return name.length > 0;
    })._each(iterator);
  },

  set: function(className) {
    this.element.className = className;
  },

  add: function(classNameToAdd) {
    if (this.include(classNameToAdd)) return;
    this.set($A(this).concat(classNameToAdd).join(' '));
  },

  remove: function(classNameToRemove) {
    if (!this.include(classNameToRemove)) return;
    this.set($A(this).without(classNameToRemove).join(' '));
  },

  toString: function() {
    return $A(this).join(' ');
  }
};

Object.extend(Element.ClassNames.prototype, Enumerable);

/*--------------------------------------------------------------------------*/

Element.addMethods();

// script.aculo.us scriptaculous.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008

// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// 
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// For details, see the script.aculo.us web site: http://script.aculo.us/

var Scriptaculous = {
  Version: '1.8.1',
  require: function(libraryName) {
    // inserting via DOM fails in Safari 2.0, so brute force approach
    document.write('<script type="text/javascript" src="'+libraryName+'"><\/script>');
  },
  REQUIRED_PROTOTYPE: '1.6.0',
  load: function() {
    function convertVersionString(versionString){
      var r = versionString.split('.');
      return parseInt(r[0])*100000 + parseInt(r[1])*1000 + parseInt(r[2]);
    }
 
    if((typeof Prototype=='undefined') || 
       (typeof Element == 'undefined') || 
       (typeof Element.Methods=='undefined') ||
       (convertVersionString(Prototype.Version) < 
        convertVersionString(Scriptaculous.REQUIRED_PROTOTYPE)))
       throw("script.aculo.us requires the Prototype JavaScript framework >= " +
        Scriptaculous.REQUIRED_PROTOTYPE);
    
    $A(document.getElementsByTagName("script")).findAll( function(s) {
      return (s.src && s.src.match(/scriptaculous\.js(\?.*)?$/))
    }).each( function(s) {
      var path = s.src.replace(/scriptaculous\.js(\?.*)?$/,'');
      var includes = s.src.match(/\?.*load=([a-z,]*)/);
      (includes ? includes[1] : 'builder,effects,dragdrop,controls,slider,sound').split(',').each(
       function(include) { Scriptaculous.require(path+include+'.js') });
    });
  }
}

Scriptaculous.load();

// script.aculo.us effects.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008

// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// Contributors:
//  Justin Palmer (http://encytemedia.com/)
//  Mark Pilgrim (http://diveintomark.org/)
//  Martin Bialasinki
// 
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/ 

// converts rgb() and #xxx to #xxxxxx format,  
// returns self (or first argument) if not convertable  
String.prototype.parseColor = function() {  
  var color = '#';
  if (this.slice(0,4) == 'rgb(') {  
    var cols = this.slice(4,this.length-1).split(',');  
    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);  
  } else {  
    if (this.slice(0,1) == '#') {  
      if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();  
      if (this.length==7) color = this.toLowerCase();  
    }  
  }  
  return (color.length==7 ? color : (arguments[0] || this));  
};

/*--------------------------------------------------------------------------*/

Element.collectTextNodes = function(element) {  
  return $A($(element).childNodes).collect( function(node) {
    return (node.nodeType==3 ? node.nodeValue : 
      (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
  }).flatten().join('');
};

Element.collectTextNodesIgnoreClass = function(element, className) {  
  return $A($(element).childNodes).collect( function(node) {
    return (node.nodeType==3 ? node.nodeValue : 
      ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? 
        Element.collectTextNodesIgnoreClass(node, className) : ''));
  }).flatten().join('');
};

Element.setContentZoom = function(element, percent) {
  element = $(element);  
  element.setStyle({fontSize: (percent/100) + 'em'});   
  if (Prototype.Browser.WebKit) window.scrollBy(0,0);
  return element;
};

Element.getInlineOpacity = function(element){
  return $(element).style.opacity || '';
};

Element.forceRerendering = function(element) {
  try {
    element = $(element);
    var n = document.createTextNode(' ');
    element.appendChild(n);
    element.removeChild(n);
  } catch(e) { }
};

/*--------------------------------------------------------------------------*/

var Effect = {
  _elementDoesNotExistError: {
    name: 'ElementDoesNotExistError',
    message: 'The specified DOM element does not exist, but is required for this effect to operate'
  },
  Transitions: {
    linear: Prototype.K,
    sinoidal: function(pos) {
      return (-Math.cos(pos*Math.PI)/2) + 0.5;
    },
    reverse: function(pos) {
      return 1-pos;
    },
    flicker: function(pos) {
      var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
      return pos > 1 ? 1 : pos;
    },
    wobble: function(pos) {
      return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
    },
    pulse: function(pos, pulses) { 
      pulses = pulses || 5; 
      return (
        ((pos % (1/pulses)) * pulses).round() == 0 ? 
              ((pos * pulses * 2) - (pos * pulses * 2).floor()) : 
          1 - ((pos * pulses * 2) - (pos * pulses * 2).floor())
        );
    },
    spring: function(pos) { 
      return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); 
    },
    none: function(pos) {
      return 0;
    },
    full: function(pos) {
      return 1;
    }
  },
  DefaultOptions: {
    duration:   1.0,   // seconds
    fps:        100,   // 100= assume 66fps max.
    sync:       false, // true for combining
    from:       0.0,
    to:         1.0,
    delay:      0.0,
    queue:      'parallel'
  },
  tagifyText: function(element) {
    var tagifyStyle = 'position:relative';
    if (Prototype.Browser.IE) tagifyStyle += ';zoom:1';
    
    element = $(element);
    $A(element.childNodes).each( function(child) {
      if (child.nodeType==3) {
        child.nodeValue.toArray().each( function(character) {
          element.insertBefore(
            new Element('span', {style: tagifyStyle}).update(
              character == ' ' ? String.fromCharCode(160) : character), 
              child);
        });
        Element.remove(child);
      }
    });
  },
  multiple: function(element, effect) {
    var elements;
    if (((typeof element == 'object') || 
        Object.isFunction(element)) && 
       (element.length))
      elements = element;
    else
      elements = $(element).childNodes;
      
    var options = Object.extend({
      speed: 0.1,
      delay: 0.0
    }, arguments[2] || { });
    var masterDelay = options.delay;

    $A(elements).each( function(element, index) {
      new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
    });
  },
  PAIRS: {
    'slide':  ['SlideDown','SlideUp'],
    'blind':  ['BlindDown','BlindUp'],
    'appear': ['Appear','Fade']
  },
  toggle: function(element, effect) {
    element = $(element);
    effect = (effect || 'appear').toLowerCase();
    var options = Object.extend({
      queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
    }, arguments[2] || { });
    Effect[element.visible() ? 
      Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
  }
};

Effect.DefaultOptions.transition = Effect.Transitions.sinoidal;

/* ------------- core effects ------------- */

Effect.ScopedQueue = Class.create(Enumerable, {
  initialize: function() {
    this.effects  = [];
    this.interval = null;    
  },
  _each: function(iterator) {
    this.effects._each(iterator);
  },
  add: function(effect) {
    var timestamp = new Date().getTime();
    
    var position = Object.isString(effect.options.queue) ? 
      effect.options.queue : effect.options.queue.position;
    
    switch(position) {
      case 'front':
        // move unstarted effects after this effect  
        this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
            e.startOn  += effect.finishOn;
            e.finishOn += effect.finishOn;
          });
        break;
      case 'with-last':
        timestamp = this.effects.pluck('startOn').max() || timestamp;
        break;
      case 'end':
        // start effect after last queued effect has finished
        timestamp = this.effects.pluck('finishOn').max() || timestamp;
        break;
    }
    
    effect.startOn  += timestamp;
    effect.finishOn += timestamp;

    if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
      this.effects.push(effect);
    
    if (!this.interval)
      this.interval = setInterval(this.loop.bind(this), 15);
  },
  remove: function(effect) {
    this.effects = this.effects.reject(function(e) { return e==effect });
    if (this.effects.length == 0) {
      clearInterval(this.interval);
      this.interval = null;
    }
  },
  loop: function() {
    var timePos = new Date().getTime();
    for(var i=0, len=this.effects.length;i<len;i++) 
      this.effects[i] && this.effects[i].loop(timePos);
  }
});

Effect.Queues = {
  instances: $H(),
  get: function(queueName) {
    if (!Object.isString(queueName)) return queueName;
    
    return this.instances.get(queueName) ||
      this.instances.set(queueName, new Effect.ScopedQueue());
  }
};
Effect.Queue = Effect.Queues.get('global');

Effect.Base = Class.create({
  position: null,
  start: function(options) {
    function codeForEvent(options,eventName){
      return (
        (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') +
        (options[eventName] ? 'this.options.'+eventName+'(this);' : '')
      );
    }
    if (options && options.transition === false) options.transition = Effect.Transitions.linear;
    this.options      = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { });
    this.currentFrame = 0;
    this.state        = 'idle';
    this.startOn      = this.options.delay*1000;
    this.finishOn     = this.startOn+(this.options.duration*1000);
    this.fromToDelta  = this.options.to-this.options.from;
    this.totalTime    = this.finishOn-this.startOn;
    this.totalFrames  = this.options.fps*this.options.duration;
    
    eval('this.render = function(pos){ '+
      'if (this.state=="idle"){this.state="running";'+
      codeForEvent(this.options,'beforeSetup')+
      (this.setup ? 'this.setup();':'')+ 
      codeForEvent(this.options,'afterSetup')+
      '};if (this.state=="running"){'+
      'pos=this.options.transition(pos)*'+this.fromToDelta+'+'+this.options.from+';'+
      'this.position=pos;'+
      codeForEvent(this.options,'beforeUpdate')+
      (this.update ? 'this.update(pos);':'')+
      codeForEvent(this.options,'afterUpdate')+
      '}}');
    
    this.event('beforeStart');
    if (!this.options.sync)
      Effect.Queues.get(Object.isString(this.options.queue) ? 
        'global' : this.options.queue.scope).add(this);
  },
  loop: function(timePos) {
    if (timePos >= this.startOn) {
      if (timePos >= this.finishOn) {
        this.render(1.0);
        this.cancel();
        this.event('beforeFinish');
        if (this.finish) this.finish(); 
        this.event('afterFinish');
        return;  
      }
      var pos   = (timePos - this.startOn) / this.totalTime,
          frame = (pos * this.totalFrames).round();
      if (frame > this.currentFrame) {
        this.render(pos);
        this.currentFrame = frame;
      }
    }
  },
  cancel: function() {
    if (!this.options.sync)
      Effect.Queues.get(Object.isString(this.options.queue) ? 
        'global' : this.options.queue.scope).remove(this);
    this.state = 'finished';
  },
  event: function(eventName) {
    if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
    if (this.options[eventName]) this.options[eventName](this);
  },
  inspect: function() {
    var data = $H();
    for(property in this)
      if (!Object.isFunction(this[property])) data.set(property, this[property]);
    return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
  }
});

Effect.Parallel = Class.create(Effect.Base, {
  initialize: function(effects) {
    this.effects = effects || [];
    this.start(arguments[1]);
  },
  update: function(position) {
    this.effects.invoke('render', position);
  },
  finish: function(position) {
    this.effects.each( function(effect) {
      effect.render(1.0);
      effect.cancel();
      effect.event('beforeFinish');
      if (effect.finish) effect.finish(position);
      effect.event('afterFinish');
    });
  }
});

Effect.Tween = Class.create(Effect.Base, {
  initialize: function(object, from, to) {
    object = Object.isString(object) ? $(object) : object;
    var args = $A(arguments), method = args.last(), 
      options = args.length == 5 ? args[3] : null;
    this.method = Object.isFunction(method) ? method.bind(object) :
      Object.isFunction(object[method]) ? object[method].bind(object) : 
      function(value) { object[method] = value };
    this.start(Object.extend({ from: from, to: to }, options || { }));
  },
  update: function(position) {
    this.method(position);
  }
});

Effect.Event = Class.create(Effect.Base, {
  initialize: function() {
    this.start(Object.extend({ duration: 0 }, arguments[0] || { }));
  },
  update: Prototype.emptyFunction
});

Effect.Opacity = Class.create(Effect.Base, {
  initialize: function(element) {
    this.element = $(element);
    if (!this.element) throw(Effect._elementDoesNotExistError);
    // make this work on IE on elements without 'layout'
    if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
      this.element.setStyle({zoom: 1});
    var options = Object.extend({
      from: this.element.getOpacity() || 0.0,
      to:   1.0
    }, arguments[1] || { });
    this.start(options);
  },
  update: function(position) {
    this.element.setOpacity(position);
  }
});

Effect.Move = Class.create(Effect.Base, {
  initialize: function(element) {
    this.element = $(element);
    if (!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({
      x:    0,
      y:    0,
      mode: 'relative'
    }, arguments[1] || { });
    this.start(options);
  },
  setup: function() {
    this.element.makePositioned();
    this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
    this.originalTop  = parseFloat(this.element.getStyle('top')  || '0');
    if (this.options.mode == 'absolute') {
      this.options.x = this.options.x - this.originalLeft;
      this.options.y = this.options.y - this.originalTop;
    }
  },
  update: function(position) {
    this.element.setStyle({
      left: (this.options.x  * position + this.originalLeft).round() + 'px',
      top:  (this.options.y  * position + this.originalTop).round()  + 'px'
    });
  }
});

// for backwards compatibility
Effect.MoveBy = function(element, toTop, toLeft) {
  return new Effect.Move(element, 
    Object.extend({ x: toLeft, y: toTop }, arguments[3] || { }));
};

Effect.Scale = Class.create(Effect.Base, {
  initialize: function(element, percent) {
    this.element = $(element);
    if (!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({
      scaleX: true,
      scaleY: true,
      scaleContent: true,
      scaleFromCenter: false,
      scaleMode: 'box',        // 'box' or 'contents' or { } with provided values
      scaleFrom: 100.0,
      scaleTo:   percent
    }, arguments[2] || { });
    this.start(options);
  },
  setup: function() {
    this.restoreAfterFinish = this.options.restoreAfterFinish || false;
    this.elementPositioning = this.element.getStyle('position');
    
    this.originalStyle = { };
    ['top','left','width','height','fontSize'].each( function(k) {
      this.originalStyle[k] = this.element.style[k];
    }.bind(this));
      
    this.originalTop  = this.element.offsetTop;
    this.originalLeft = this.element.offsetLeft;
    
    var fontSize = this.element.getStyle('font-size') || '100%';
    ['em','px','%','pt'].each( function(fontSizeType) {
      if (fontSize.indexOf(fontSizeType)>0) {
        this.fontSize     = parseFloat(fontSize);
        this.fontSizeType = fontSizeType;
      }
    }.bind(this));
    
    this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
    
    this.dims = null;
    if (this.options.scaleMode=='box')
      this.dims = [this.element.offsetHeight, this.element.offsetWidth];
    if (/^content/.test(this.options.scaleMode))
      this.dims = [this.element.scrollHeight, this.element.scrollWidth];
    if (!this.dims)
      this.dims = [this.options.scaleMode.originalHeight,
                   this.options.scaleMode.originalWidth];
  },
  update: function(position) {
    var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
    if (this.options.scaleContent && this.fontSize)
      this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
    this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
  },
  finish: function(position) {
    if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
  },
  setDimensions: function(height, width) {
    var d = { };
    if (this.options.scaleX) d.width = width.round() + 'px';
    if (this.options.scaleY) d.height = height.round() + 'px';
    if (this.options.scaleFromCenter) {
      var topd  = (height - this.dims[0])/2;
      var leftd = (width  - this.dims[1])/2;
      if (this.elementPositioning == 'absolute') {
        if (this.options.scaleY) d.top = this.originalTop-topd + 'px';
        if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
      } else {
        if (this.options.scaleY) d.top = -topd + 'px';
        if (this.options.scaleX) d.left = -leftd + 'px';
      }
    }
    this.element.setStyle(d);
  }
});

Effect.Highlight = Class.create(Effect.Base, {
  initialize: function(element) {
    this.element = $(element);
    if (!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { });
    this.start(options);
  },
  setup: function() {
    // Prevent executing on elements not in the layout flow
    if (this.element.getStyle('display')=='none') { this.cancel(); return; }
    // Disable background image during the effect
    this.oldStyle = { };
    if (!this.options.keepBackgroundImage) {
      this.oldStyle.backgroundImage = this.element.getStyle('background-image');
      this.element.setStyle({backgroundImage: 'none'});
    }
    if (!this.options.endcolor)
      this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
    if (!this.options.restorecolor)
      this.options.restorecolor = this.element.getStyle('background-color');
    // init color calculations
    this._base  = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
    this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
  },
  update: function(position) {
    this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
      return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) });
  },
  finish: function() {
    this.element.setStyle(Object.extend(this.oldStyle, {
      backgroundColor: this.options.restorecolor
    }));
  }
});

Effect.ScrollTo = function(element) {
  var options = arguments[1] || { },
    scrollOffsets = document.viewport.getScrollOffsets(),
    elementOffsets = $(element).cumulativeOffset(),
    max = (window.height || document.body.scrollHeight) - document.viewport.getHeight();  

  if (options.offset) elementOffsets[1] += options.offset;

  return new Effect.Tween(null,
    scrollOffsets.top,
    elementOffsets[1] > max ? max : elementOffsets[1],
    options,
    function(p){ scrollTo(scrollOffsets.left, p.round()) }
  );
};

/* ------------- combination effects ------------- */

Effect.Fade = function(element) {
  element = $(element);
  var oldOpacity = element.getInlineOpacity();
  var options = Object.extend({
    from: element.getOpacity() || 1.0,
    to:   0.0,
    afterFinishInternal: function(effect) { 
      if (effect.options.to!=0) return;
      effect.element.hide().setStyle({opacity: oldOpacity}); 
    }
  }, arguments[1] || { });
  return new Effect.Opacity(element,options);
};

Effect.Appear = function(element) {
  element = $(element);
  var options = Object.extend({
  from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
  to:   1.0,
  // force Safari to render floated elements properly
  afterFinishInternal: function(effect) {
    effect.element.forceRerendering();
  },
  beforeSetup: function(effect) {
    effect.element.setOpacity(effect.options.from).show(); 
  }}, arguments[1] || { });
  return new Effect.Opacity(element,options);
};

Effect.Puff = function(element) {
  element = $(element);
  var oldStyle = { 
    opacity: element.getInlineOpacity(), 
    position: element.getStyle('position'),
    top:  element.style.top,
    left: element.style.left,
    width: element.style.width,
    height: element.style.height
  };
  return new Effect.Parallel(
   [ new Effect.Scale(element, 200, 
      { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), 
     new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], 
     Object.extend({ duration: 1.0, 
      beforeSetupInternal: function(effect) {
        Position.absolutize(effect.effects[0].element)
      },
      afterFinishInternal: function(effect) {
         effect.effects[0].element.hide().setStyle(oldStyle); }
     }, arguments[1] || { })
   );
};

Effect.BlindUp = function(element) {
  element = $(element);
  element.makeClipping();
  return new Effect.Scale(element, 0,
    Object.extend({ scaleContent: false, 
      scaleX: false, 
      restoreAfterFinish: true,
      afterFinishInternal: function(effect) {
        effect.element.hide().undoClipping();
      } 
    }, arguments[1] || { })
  );
};

Effect.BlindDown = function(element) {
  element = $(element);
  var elementDimensions = element.getDimensions();
  return new Effect.Scale(element, 100, Object.extend({ 
    scaleContent: false, 
    scaleX: false,
    scaleFrom: 0,
    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
    restoreAfterFinish: true,
    afterSetup: function(effect) {
      effect.element.makeClipping().setStyle({height: '0px'}).show(); 
    },  
    afterFinishInternal: function(effect) {
      effect.element.undoClipping();
    }
  }, arguments[1] || { }));
};

Effect.SwitchOff = function(element) {
  element = $(element);
  var oldOpacity = element.getInlineOpacity();
  return new Effect.Appear(element, Object.extend({
    duration: 0.4,
    from: 0,
    transition: Effect.Transitions.flicker,
    afterFinishInternal: function(effect) {
      new Effect.Scale(effect.element, 1, { 
        duration: 0.3, scaleFromCenter: true,
        scaleX: false, scaleContent: false, restoreAfterFinish: true,
        beforeSetup: function(effect) { 
          effect.element.makePositioned().makeClipping();
        },
        afterFinishInternal: function(effect) {
          effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
        }
      })
    }
  }, arguments[1] || { }));
};

Effect.DropOut = function(element) {
  element = $(element);
  var oldStyle = {
    top: element.getStyle('top'),
    left: element.getStyle('left'),
    opacity: element.getInlineOpacity() };
  return new Effect.Parallel(
    [ new Effect.Move(element, {x: 0, y: 100, sync: true }), 
      new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
    Object.extend(
      { duration: 0.5,
        beforeSetup: function(effect) {
          effect.effects[0].element.makePositioned(); 
        },
        afterFinishInternal: function(effect) {
          effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
        } 
      }, arguments[1] || { }));
};

Effect.Shake = function(element) {
  element = $(element);
  var options = Object.extend({
    distance: 20,
    duration: 0.5
  }, arguments[1] || {});
  var distance = parseFloat(options.distance);
  var split = parseFloat(options.duration) / 10.0;
  var oldStyle = {
    top: element.getStyle('top'),
    left: element.getStyle('left') };
    return new Effect.Move(element,
      { x:  distance, y: 0, duration: split, afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) {
        effect.element.undoPositioned().setStyle(oldStyle);
  }}) }}) }}) }}) }}) }});
};

Effect.SlideDown = function(element) {
  element = $(element).cleanWhitespace();
  // SlideDown need to have the content of the element wrapped in a container element with fixed height!
  var oldInnerBottom = element.down().getStyle('bottom');
  var elementDimensions = element.getDimensions();
  return new Effect.Scale(element, 100, Object.extend({ 
    scaleContent: false, 
    scaleX: false, 
    scaleFrom: window.opera ? 0 : 1,
    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
    restoreAfterFinish: true,
    afterSetup: function(effect) {
      effect.element.makePositioned();
      effect.element.down().makePositioned();
      if (window.opera) effect.element.setStyle({top: ''});
      effect.element.makeClipping().setStyle({height: '0px'}).show(); 
    },
    afterUpdateInternal: function(effect) {
      effect.element.down().setStyle({bottom:
        (effect.dims[0] - effect.element.clientHeight) + 'px' }); 
    },
    afterFinishInternal: function(effect) {
      effect.element.undoClipping().undoPositioned();
      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
    }, arguments[1] || { })
  );
};

Effect.SlideUp = function(element) {
  element = $(element).cleanWhitespace();
  var oldInnerBottom = element.down().getStyle('bottom');
  var elementDimensions = element.getDimensions();
  return new Effect.Scale(element, window.opera ? 0 : 1,
   Object.extend({ scaleContent: false, 
    scaleX: false, 
    scaleMode: 'box',
    scaleFrom: 100,
    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
    restoreAfterFinish: true,
    afterSetup: function(effect) {
      effect.element.makePositioned();
      effect.element.down().makePositioned();
      if (window.opera) effect.element.setStyle({top: ''});
      effect.element.makeClipping().show();
    },  
    afterUpdateInternal: function(effect) {
      effect.element.down().setStyle({bottom:
        (effect.dims[0] - effect.element.clientHeight) + 'px' });
    },
    afterFinishInternal: function(effect) {
      effect.element.hide().undoClipping().undoPositioned();
      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom});
    }
   }, arguments[1] || { })
  );
};

// Bug in opera makes the TD containing this element expand for a instance after finish 
Effect.Squish = function(element) {
  return new Effect.Scale(element, window.opera ? 1 : 0, { 
    restoreAfterFinish: true,
    beforeSetup: function(effect) {
      effect.element.makeClipping(); 
    },  
    afterFinishInternal: function(effect) {
      effect.element.hide().undoClipping(); 
    }
  });
};

Effect.Grow = function(element) {
  element = $(element);
  var options = Object.extend({
    direction: 'center',
    moveTransition: Effect.Transitions.sinoidal,
    scaleTransition: Effect.Transitions.sinoidal,
    opacityTransition: Effect.Transitions.full
  }, arguments[1] || { });
  var oldStyle = {
    top: element.style.top,
    left: element.style.left,
    height: element.style.height,
    width: element.style.width,
    opacity: element.getInlineOpacity() };

  var dims = element.getDimensions();    
  var initialMoveX, initialMoveY;
  var moveX, moveY;
  
  switch (options.direction) {
    case 'top-left':
      initialMoveX = initialMoveY = moveX = moveY = 0; 
      break;
    case 'top-right':
      initialMoveX = dims.width;
      initialMoveY = moveY = 0;
      moveX = -dims.width;
      break;
    case 'bottom-left':
      initialMoveX = moveX = 0;
      initialMoveY = dims.height;
      moveY = -dims.height;
      break;
    case 'bottom-right':
      initialMoveX = dims.width;
      initialMoveY = dims.height;
      moveX = -dims.width;
      moveY = -dims.height;
      break;
    case 'center':
      initialMoveX = dims.width / 2;
      initialMoveY = dims.height / 2;
      moveX = -dims.width / 2;
      moveY = -dims.height / 2;
      break;
  }
  
  return new Effect.Move(element, {
    x: initialMoveX,
    y: initialMoveY,
    duration: 0.01, 
    beforeSetup: function(effect) {
      effect.element.hide().makeClipping().makePositioned();
    },
    afterFinishInternal: function(effect) {
      new Effect.Parallel(
        [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
          new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
          new Effect.Scale(effect.element, 100, {
            scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, 
            sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
        ], Object.extend({
             beforeSetup: function(effect) {
               effect.effects[0].element.setStyle({height: '0px'}).show(); 
             },
             afterFinishInternal: function(effect) {
               effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); 
             }
           }, options)
      )
    }
  });
};

Effect.Shrink = function(element) {
  element = $(element);
  var options = Object.extend({
    direction: 'center',
    moveTransition: Effect.Transitions.sinoidal,
    scaleTransition: Effect.Transitions.sinoidal,
    opacityTransition: Effect.Transitions.none
  }, arguments[1] || { });
  var oldStyle = {
    top: element.style.top,
    left: element.style.left,
    height: element.style.height,
    width: element.style.width,
    opacity: element.getInlineOpacity() };

  var dims = element.getDimensions();
  var moveX, moveY;
  
  switch (options.direction) {
    case 'top-left':
      moveX = moveY = 0;
      break;
    case 'top-right':
      moveX = dims.width;
      moveY = 0;
      break;
    case 'bottom-left':
      moveX = 0;
      moveY = dims.height;
      break;
    case 'bottom-right':
      moveX = dims.width;
      moveY = dims.height;
      break;
    case 'center':  
      moveX = dims.width / 2;
      moveY = dims.height / 2;
      break;
  }
  
  return new Effect.Parallel(
    [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
      new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
      new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
    ], Object.extend({            
         beforeStartInternal: function(effect) {
           effect.effects[0].element.makePositioned().makeClipping(); 
         },
         afterFinishInternal: function(effect) {
           effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
       }, options)
  );
};

Effect.Pulsate = function(element) {
  element = $(element);
  var options    = arguments[1] || { };
  var oldOpacity = element.getInlineOpacity();
  var transition = options.transition || Effect.Transitions.sinoidal;
  var reverser   = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) };
  reverser.bind(transition);
  return new Effect.Opacity(element, 
    Object.extend(Object.extend({  duration: 2.0, from: 0,
      afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
    }, options), {transition: reverser}));
};

Effect.Fold = function(element) {
  element = $(element);
  var oldStyle = {
    top: element.style.top,
    left: element.style.left,
    width: element.style.width,
    height: element.style.height };
  element.makeClipping();
  return new Effect.Scale(element, 5, Object.extend({   
    scaleContent: false,
    scaleX: false,
    afterFinishInternal: function(effect) {
    new Effect.Scale(element, 1, { 
      scaleContent: false, 
      scaleY: false,
      afterFinishInternal: function(effect) {
        effect.element.hide().undoClipping().setStyle(oldStyle);
      } });
  }}, arguments[1] || { }));
};

Effect.Morph = Class.create(Effect.Base, {
  initialize: function(element) {
    this.element = $(element);
    if (!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({
      style: { }
    }, arguments[1] || { });
    
    if (!Object.isString(options.style)) this.style = $H(options.style);
    else {
      if (options.style.include(':'))
        this.style = options.style.parseStyle();
      else {
        this.element.addClassName(options.style);
        this.style = $H(this.element.getStyles());
        this.element.removeClassName(options.style);
        var css = this.element.getStyles();
        this.style = this.style.reject(function(style) {
          return style.value == css[style.key];
        });
        options.afterFinishInternal = function(effect) {
          effect.element.addClassName(effect.options.style);
          effect.transforms.each(function(transform) {
            effect.element.style[transform.style] = '';
          });
        }
      }
    }
    this.start(options);
  },
  
  setup: function(){
    function parseColor(color){
      if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
      color = color.parseColor();
      return $R(0,2).map(function(i){
        return parseInt( color.slice(i*2+1,i*2+3), 16 ) 
      });
    }
    this.transforms = this.style.map(function(pair){
      var property = pair[0], value = pair[1], unit = null;

      if (value.parseColor('#zzzzzz') != '#zzzzzz') {
        value = value.parseColor();
        unit  = 'color';
      } else if (property == 'opacity') {
        value = parseFloat(value);
        if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
          this.element.setStyle({zoom: 1});
      } else if (Element.CSS_LENGTH.test(value)) {
          var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
          value = parseFloat(components[1]);
          unit = (components.length == 3) ? components[2] : null;
      }

      var originalValue = this.element.getStyle(property);
      return { 
        style: property.camelize(), 
        originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), 
        targetValue: unit=='color' ? parseColor(value) : value,
        unit: unit
      };
    }.bind(this)).reject(function(transform){
      return (
        (transform.originalValue == transform.targetValue) ||
        (
          transform.unit != 'color' &&
          (isNaN(transform.originalValue) || isNaN(transform.targetValue))
        )
      )
    });
  },
  update: function(position) {
    var style = { }, transform, i = this.transforms.length;
    while(i--)
      style[(transform = this.transforms[i]).style] = 
        transform.unit=='color' ? '#'+
          (Math.round(transform.originalValue[0]+
            (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
          (Math.round(transform.originalValue[1]+
            (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
          (Math.round(transform.originalValue[2]+
            (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
        (transform.originalValue +
          (transform.targetValue - transform.originalValue) * position).toFixed(3) + 
            (transform.unit === null ? '' : transform.unit);
    this.element.setStyle(style, true);
  }
});

Effect.Transform = Class.create({
  initialize: function(tracks){
    this.tracks  = [];
    this.options = arguments[1] || { };
    this.addTracks(tracks);
  },
  addTracks: function(tracks){
    tracks.each(function(track){
      track = $H(track);
      var data = track.values().first();
      this.tracks.push($H({
        ids:     track.keys().first(),
        effect:  Effect.Morph,
        options: { style: data }
      }));
    }.bind(this));
    return this;
  },
  play: function(){
    return new Effect.Parallel(
      this.tracks.map(function(track){
        var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options');
        var elements = [$(ids) || $$(ids)].flatten();
        return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) });
      }).flatten(),
      this.options
    );
  }
});

Element.CSS_PROPERTIES = $w(
  'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + 
  'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
  'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
  'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
  'fontSize fontWeight height left letterSpacing lineHeight ' +
  'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
  'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
  'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
  'right textIndent top width wordSpacing zIndex');
  
Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;

String.__parseStyleElement = document.createElement('div');
String.prototype.parseStyle = function(){
  var style, styleRules = $H();
  if (Prototype.Browser.WebKit)
    style = new Element('div',{style:this}).style;
  else {
    String.__parseStyleElement.innerHTML = '<div style="' + this + '"></div>';
    style = String.__parseStyleElement.childNodes[0].style;
  }
  
  Element.CSS_PROPERTIES.each(function(property){
    if (style[property]) styleRules.set(property, style[property]); 
  });
  
  if (Prototype.Browser.IE && this.include('opacity'))
    styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);

  return styleRules;
};

if (document.defaultView && document.defaultView.getComputedStyle) {
  Element.getStyles = function(element) {
    var css = document.defaultView.getComputedStyle($(element), null);
    return Element.CSS_PROPERTIES.inject({ }, function(styles, property) {
      styles[property] = css[property];
      return styles;
    });
  };
} else {
  Element.getStyles = function(element) {
    element = $(element);
    var css = element.currentStyle, styles;
    styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) {
      results[property] = css[property];
      return results;
    });
    if (!styles.opacity) styles.opacity = element.getOpacity();
    return styles;
  };
};

Effect.Methods = {
  morph: function(element, style) {
    element = $(element);
    new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { }));
    return element;
  },
  visualEffect: function(element, effect, options) {
    element = $(element)
    var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);
    new Effect[klass](element, options);
    return element;
  },
  highlight: function(element, options) {
    element = $(element);
    new Effect.Highlight(element, options);
    return element;
  }
};

$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
  'pulsate shake puff squish switchOff dropOut').each(
  function(effect) { 
    Effect.Methods[effect] = function(element, options){
      element = $(element);
      Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);
      return element;
    }
  }
);

$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each( 
  function(f) { Effect.Methods[f] = Element[f]; }
);

Element.addMethods(Effect.Methods);


// script.aculo.us controls.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008

// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//           (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
//           (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
// Contributors:
//  Richard Livsey
//  Rahul Bhargava
//  Rob Wills
// 
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/

// Autocompleter.Base handles all the autocompletion functionality 
// that's independent of the data source for autocompletion. This
// includes drawing the autocompletion menu, observing keyboard
// and mouse events, and similar.
//
// Specific autocompleters need to provide, at the very least, 
// a getUpdatedChoices function that will be invoked every time
// the text inside the monitored textbox changes. This method 
// should get the text for which to provide autocompletion by
// invoking this.getToken(), NOT by directly accessing
// this.element.value. This is to allow incremental tokenized
// autocompletion. Specific auto-completion logic (AJAX, etc)
// belongs in getUpdatedChoices.
//
// Tokenized incremental autocompletion is enabled automatically
// when an autocompleter is instantiated with the 'tokens' option
// in the options parameter, e.g.:
// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
// will incrementally autocomplete with a comma as the token.
// Additionally, ',' in the above example can be replaced with
// a token array, e.g. { tokens: [',', '\n'] } which
// enables autocompletion on multiple tokens. This is most 
// useful when one of the tokens is \n (a newline), as it 
// allows smart autocompletion after linebreaks.

if(typeof Effect == 'undefined')
  throw("controls.js requires including script.aculo.us' effects.js library");

var Autocompleter = { }
Autocompleter.Base = Class.create({
  baseInitialize: function(element, update, options) {
    element          = $(element)
    this.element     = element; 
    this.update      = $(update);  
    this.hasFocus    = false; 
    this.changed     = false; 
    this.active      = false; 
    this.index       = 0;     
    this.entryCount  = 0;
    this.oldElementValue = this.element.value;

    if(this.setOptions)
      this.setOptions(options);
    else
      this.options = options || { };

    this.options.paramName    = this.options.paramName || this.element.name;
    this.options.tokens       = this.options.tokens || [];
    this.options.frequency    = this.options.frequency || 0.4;
    this.options.minChars     = this.options.minChars || 1;
    this.options.onShow       = this.options.onShow || 
      function(element, update){ 
        if(!update.style.position || update.style.position=='absolute') {
          update.style.position = 'absolute';
          Position.clone(element, update, {
            setHeight: false, 
            offsetTop: element.offsetHeight
          });
        }
        Effect.Appear(update,{duration:0.15});
      };
    this.options.onHide = this.options.onHide || 
      function(element, update){ new Effect.Fade(update,{duration:0.15}) };

    if(typeof(this.options.tokens) == 'string') 
      this.options.tokens = new Array(this.options.tokens);
    // Force carriage returns as token delimiters anyway
    if (!this.options.tokens.include('\n'))
      this.options.tokens.push('\n');

    this.observer = null;
    
    this.element.setAttribute('autocomplete','off');

    Element.hide(this.update);

    Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
    Event.observe(this.element, 'keypress', this.onKeyPress.bindAsEventListener(this));
  },

  show: function() {
    if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
    if(!this.iefix && 
      (Prototype.Browser.IE) &&
      (Element.getStyle(this.update, 'position')=='absolute')) {
      new Insertion.After(this.update, 
       '<iframe id="' + this.update.id + '_iefix" '+
       'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
       'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
      this.iefix = $(this.update.id+'_iefix');
    }
    if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
  },
  
  fixIEOverlapping: function() {
    Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
    this.iefix.style.zIndex = 1;
    this.update.style.zIndex = 2;
    Element.show(this.iefix);
  },

  hide: function() {
    this.stopIndicator();
    if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
    if(this.iefix) Element.hide(this.iefix);
  },

  startIndicator: function() {
    if(this.options.indicator) Element.show(this.options.indicator);
  },

  stopIndicator: function() {
    if(this.options.indicator) Element.hide(this.options.indicator);
  },

  onKeyPress: function(event) {
    if(this.active)
      switch(event.keyCode) {
       case Event.KEY_TAB:
       case Event.KEY_RETURN:
         this.selectEntry();
         Event.stop(event);
       case Event.KEY_ESC:
         this.hide();
         this.active = false;
         Event.stop(event);
         return;
       case Event.KEY_LEFT:
       case Event.KEY_RIGHT:
         return;
       case Event.KEY_UP:
         this.markPrevious();
         this.render();
         Event.stop(event);
         return;
       case Event.KEY_DOWN:
         this.markNext();
         this.render();
         Event.stop(event);
         return;
      }
     else 
       if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || 
         (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;

    this.changed = true;
    this.hasFocus = true;

    if(this.observer) clearTimeout(this.observer);
      this.observer = 
        setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
  },

  activate: function() {
    this.changed = false;
    this.hasFocus = true;
    this.getUpdatedChoices();
  },

  onHover: function(event) {
    var element = Event.findElement(event, 'LI');
    if(this.index != element.autocompleteIndex) 
    {
        this.index = element.autocompleteIndex;
        this.render();
    }
    Event.stop(event);
  },
  
  onClick: function(event) {
    var element = Event.findElement(event, 'LI');
    this.index = element.autocompleteIndex;
    this.selectEntry();
    this.hide();
  },
  
  onBlur: function(event) {
    // needed to make click events working
    setTimeout(this.hide.bind(this), 250);
    this.hasFocus = false;
    this.active = false;     
  }, 
  
  render: function() {
    if(this.entryCount > 0) {
      for (var i = 0; i < this.entryCount; i++)
        this.index==i ? 
          Element.addClassName(this.getEntry(i),"selected") : 
          Element.removeClassName(this.getEntry(i),"selected");
      if(this.hasFocus) { 
        this.show();
        this.active = true;
      }
    } else {
      this.active = false;
      this.hide();
    }
  },
  
  markPrevious: function() {
    if(this.index > 0) this.index--
      else this.index = this.entryCount-1;
    this.getEntry(this.index).scrollIntoView(true);
  },
  
  markNext: function() {
    if(this.index < this.entryCount-1) this.index++
      else this.index = 0;
    this.getEntry(this.index).scrollIntoView(false);
  },
  
  getEntry: function(index) {
    return this.update.firstChild.childNodes[index];
  },
  
  getCurrentEntry: function() {
    return this.getEntry(this.index);
  },
  
  selectEntry: function() {
    this.active = false;
    this.updateElement(this.getCurrentEntry());
  },

  updateElement: function(selectedElement) {
    if (this.options.updateElement) {
      this.options.updateElement(selectedElement);
      return;
    }
    var value = '';
    if (this.options.select) {
      var nodes = $(selectedElement).select('.' + this.options.select) || [];
      if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
    } else
      value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
    
    var bounds = this.getTokenBounds();
    if (bounds[0] != -1) {
      var newValue = this.element.value.substr(0, bounds[0]);
      var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
      if (whitespace)
        newValue += whitespace[0];
      this.element.value = newValue + value + this.element.value.substr(bounds[1]);
    } else {
      this.element.value = value;
    }
    this.oldElementValue = this.element.value;
    this.element.focus();
    
    if (this.options.afterUpdateElement)
      this.options.afterUpdateElement(this.element, selectedElement);
  },

  updateChoices: function(choices) {
    if(!this.changed && this.hasFocus) {
      this.update.innerHTML = choices;
      Element.cleanWhitespace(this.update);
      Element.cleanWhitespace(this.update.down());

      if(this.update.firstChild && this.update.down().childNodes) {
        this.entryCount = 
          this.update.down().childNodes.length;
        for (var i = 0; i < this.entryCount; i++) {
          var entry = this.getEntry(i);
          entry.autocompleteIndex = i;
          this.addObservers(entry);
        }
      } else { 
        this.entryCount = 0;
      }

      this.stopIndicator();
      this.index = 0;
      
      if(this.entryCount==1 && this.options.autoSelect) {
        this.selectEntry();
        this.hide();
      } else {
        this.render();
      }
    }
  },

  addObservers: function(element) {
    Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
    Event.observe(element, "click", this.onClick.bindAsEventListener(this));
  },

  onObserverEvent: function() {
    this.changed = false;   
    this.tokenBounds = null;
    if(this.getToken().length>=this.options.minChars) {
      this.getUpdatedChoices();
    } else {
      this.active = false;
      this.hide();
    }
    this.oldElementValue = this.element.value;
  },

  getToken: function() {
    var bounds = this.getTokenBounds();
    return this.element.value.substring(bounds[0], bounds[1]).strip();
  },

  getTokenBounds: function() {
    if (null != this.tokenBounds) return this.tokenBounds;
    var value = this.element.value;
    if (value.strip().empty()) return [-1, 0];
    var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
    var offset = (diff == this.oldElementValue.length ? 1 : 0);
    var prevTokenPos = -1, nextTokenPos = value.length;
    var tp;
    for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
      tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
      if (tp > prevTokenPos) prevTokenPos = tp;
      tp = value.indexOf(this.options.tokens[index], diff + offset);
      if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
    }
    return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
  }
});

Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
  var boundary = Math.min(newS.length, oldS.length);
  for (var index = 0; index < boundary; ++index)
    if (newS[index] != oldS[index])
      return index;
  return boundary;
};

Ajax.Autocompleter = Class.create(Autocompleter.Base, {
  initialize: function(element, update, url, options) {
    this.baseInitialize(element, update, options);
    this.options.asynchronous  = true;
    this.options.onComplete    = this.onComplete.bind(this);
    this.options.defaultParams = this.options.parameters || null;
    this.url                   = url;
  },

  getUpdatedChoices: function() {
    this.startIndicator();
    
    var entry = encodeURIComponent(this.options.paramName) + '=' + 
      encodeURIComponent(this.getToken());

    this.options.parameters = this.options.callback ?
      this.options.callback(this.element, entry) : entry;

    if(this.options.defaultParams) 
      this.options.parameters += '&' + this.options.defaultParams;
    
    new Ajax.Request(this.url, this.options);
  },

  onComplete: function(request) {
    this.updateChoices(request.responseText);
  }
});

// The local array autocompleter. Used when you'd prefer to
// inject an array of autocompletion options into the page, rather
// than sending out Ajax queries, which can be quite slow sometimes.
//
// The constructor takes four parameters. The first two are, as usual,
// the id of the monitored textbox, and id of the autocompletion menu.
// The third is the array you want to autocomplete from, and the fourth
// is the options block.
//
// Extra local autocompletion options:
// - choices - How many autocompletion choices to offer
//
// - partialSearch - If false, the autocompleter will match entered
//                    text only at the beginning of strings in the 
//                    autocomplete array. Defaults to true, which will
//                    match text at the beginning of any *word* in the
//                    strings in the autocomplete array. If you want to
//                    search anywhere in the string, additionally set
//                    the option fullSearch to true (default: off).
//
// - fullSsearch - Search anywhere in autocomplete array strings.
//
// - partialChars - How many characters to enter before triggering
//                   a partial match (unlike minChars, which defines
//                   how many characters are required to do any match
//                   at all). Defaults to 2.
//
// - ignoreCase - Whether to ignore case when autocompleting.
//                 Defaults to true.
//
// It's possible to pass in a custom function as the 'selector' 
// option, if you prefer to write your own autocompletion logic.
// In that case, the other options above will not apply unless
// you support them.

Autocompleter.Local = Class.create(Autocompleter.Base, {
  initialize: function(element, update, array, options) {
    this.baseInitialize(element, update, options);
    this.options.array = array;
  },

  getUpdatedChoices: function() {
    this.updateChoices(this.options.selector(this));
  },

  setOptions: function(options) {
    this.options = Object.extend({
      choices: 10,
      partialSearch: true,
      partialChars: 2,
      ignoreCase: true,
      fullSearch: false,
      selector: function(instance) {
        var ret       = []; // Beginning matches
        var partial   = []; // Inside matches
        var entry     = instance.getToken();
        var count     = 0;

        for (var i = 0; i < instance.options.array.length &&  
          ret.length < instance.options.choices ; i++) { 

          var elem = instance.options.array[i];
          var foundPos = instance.options.ignoreCase ? 
            elem.toLowerCase().indexOf(entry.toLowerCase()) : 
            elem.indexOf(entry);

          while (foundPos != -1) {
            if (foundPos == 0 && elem.length != entry.length) { 
              ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" + 
                elem.substr(entry.length) + "</li>");
              break;
            } else if (entry.length >= instance.options.partialChars && 
              instance.options.partialSearch && foundPos != -1) {
              if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
                partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
                  elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
                  foundPos + entry.length) + "</li>");
                break;
              }
            }

            foundPos = instance.options.ignoreCase ? 
              elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : 
              elem.indexOf(entry, foundPos + 1);

          }
        }
        if (partial.length)
          ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
        return "<ul>" + ret.join('') + "</ul>";
      }
    }, options || { });
  }
});

// AJAX in-place editor and collection editor
// Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007).

// Use this if you notice weird scrolling problems on some browsers,
// the DOM might be a bit confused when this gets called so do this
// waits 1 ms (with setTimeout) until it does the activation
Field.scrollFreeActivate = function(field) {
  setTimeout(function() {
    Field.activate(field);
  }, 1);
}

Ajax.InPlaceEditor = Class.create({
  initialize: function(element, url, options) {
    this.url = url;
    this.element = element = $(element);
    this.prepareOptions();
    this._controls = { };
    arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
    Object.extend(this.options, options || { });
    if (!this.options.formId && this.element.id) {
      this.options.formId = this.element.id + '-inplaceeditor';
      if ($(this.options.formId))
        this.options.formId = '';
    }
    if (this.options.externalControl)
      this.options.externalControl = $(this.options.externalControl);
    if (!this.options.externalControl)
      this.options.externalControlOnly = false;
    this._originalBackground = this.element.getStyle('background-color') || 'transparent';
    this.element.title = this.options.clickToEditText;
    this._boundCancelHandler = this.handleFormCancellation.bind(this);
    this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
    this._boundFailureHandler = this.handleAJAXFailure.bind(this);
    this._boundSubmitHandler = this.handleFormSubmission.bind(this);
    this._boundWrapperHandler = this.wrapUp.bind(this);
    this.registerListeners();
  },
  checkForEscapeOrReturn: function(e) {
    if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
    if (Event.KEY_ESC == e.keyCode)
      this.handleFormCancellation(e);
    else if (Event.KEY_RETURN == e.keyCode)
      this.handleFormSubmission(e);
  },
  createControl: function(mode, handler, extraClasses) {
    var control = this.options[mode + 'Control'];
    var text = this.options[mode + 'Text'];
    if ('button' == control) {
      var btn = document.createElement('input');
      btn.type = 'submit';
      btn.value = text;
      btn.className = 'editor_' + mode + '_button';
      if ('cancel' == mode)
        btn.onclick = this._boundCancelHandler;
      this._form.appendChild(btn);
      this._controls[mode] = btn;
    } else if ('link' == control) {
      var link = document.createElement('a');
      link.href = '#';
      link.appendChild(document.createTextNode(text));
      link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
      link.className = 'editor_' + mode + '_link';
      if (extraClasses)
        link.className += ' ' + extraClasses;
      this._form.appendChild(link);
      this._controls[mode] = link;
    }
  },
  createEditField: function() {
    var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
    var fld;
    if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
      fld = document.createElement('input');
      fld.type = 'text';
      var size = this.options.size || this.options.cols || 0;
      if (0 < size) fld.size = size;
    } else {
      fld = document.createElement('textarea');
      fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
      fld.cols = this.options.cols || 40;
    }
    fld.name = this.options.paramName;
    fld.value = text; // No HTML breaks conversion anymore
    fld.className = 'editor_field';
    if (this.options.submitOnBlur)
      fld.onblur = this._boundSubmitHandler;
    this._controls.editor = fld;
    if (this.options.loadTextURL)
      this.loadExternalText();
    this._form.appendChild(this._controls.editor);
  },
  createForm: function() {
    var ipe = this;
    function addText(mode, condition) {
      var text = ipe.options['text' + mode + 'Controls'];
      if (!text || condition === false) return;
      ipe._form.appendChild(document.createTextNode(text));
    };
    this._form = $(document.createElement('form'));
    this._form.id = this.options.formId;
    this._form.addClassName(this.options.formClassName);
    this._form.onsubmit = this._boundSubmitHandler;
    this.createEditField();
    if ('textarea' == this._controls.editor.tagName.toLowerCase())
      this._form.appendChild(document.createElement('br'));
    if (this.options.onFormCustomization)
      this.options.onFormCustomization(this, this._form);
    addText('Before', this.options.okControl || this.options.cancelControl);
    this.createControl('ok', this._boundSubmitHandler);
    addText('Between', this.options.okControl && this.options.cancelControl);
    this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
    addText('After', this.options.okControl || this.options.cancelControl);
  },
  destroy: function() {
    if (this._oldInnerHTML)
      this.element.innerHTML = this._oldInnerHTML;
    this.leaveEditMode();
    this.unregisterListeners();
  },
  enterEditMode: function(e) {
    if (this._saving || this._editing) return;
    this._editing = true;
    this.triggerCallback('onEnterEditMode');
    if (this.options.externalControl)
      this.options.externalControl.hide();
    this.element.hide();
    this.createForm();
    this.element.parentNode.insertBefore(this._form, this.element);
    if (!this.options.loadTextURL)
      this.postProcessEditField();
    if (e) Event.stop(e);
  },
  enterHover: function(e) {
    if (this.options.hoverClassName)
      this.element.addClassName(this.options.hoverClassName);
    if (this._saving) return;
    this.triggerCallback('onEnterHover');
  },
  getText: function() {
    return this.element.innerHTML;
  },
  handleAJAXFailure: function(transport) {
    this.triggerCallback('onFailure', transport);
    if (this._oldInnerHTML) {
      this.element.innerHTML = this._oldInnerHTML;
      this._oldInnerHTML = null;
    }
  },
  handleFormCancellation: function(e) {
    this.wrapUp();
    if (e) Event.stop(e);
  },
  handleFormSubmission: function(e) {
    var form = this._form;
    var value = $F(this._controls.editor);
    this.prepareSubmission();
    var params = this.options.callback(form, value) || '';
    if (Object.isString(params))
      params = params.toQueryParams();
    params.editorId = this.element.id;
    if (this.options.htmlResponse) {
      var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
      Object.extend(options, {
        parameters: params,
        onComplete: this._boundWrapperHandler,
        onFailure: this._boundFailureHandler
      });
      new Ajax.Updater({ success: this.element }, this.url, options);
    } else {
      var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
      Object.extend(options, {
        parameters: params,
        onComplete: this._boundWrapperHandler,
        onFailure: this._boundFailureHandler
      });
      new Ajax.Request(this.url, options);
    }
    if (e) Event.stop(e);
  },
  leaveEditMode: function() {
    this.element.removeClassName(this.options.savingClassName);
    this.removeForm();
    this.leaveHover();
    this.element.style.backgroundColor = this._originalBackground;
    this.element.show();
    if (this.options.externalControl)
      this.options.externalControl.show();
    this._saving = false;
    this._editing = false;
    this._oldInnerHTML = null;
    this.triggerCallback('onLeaveEditMode');
  },
  leaveHover: function(e) {
    if (this.options.hoverClassName)
      this.element.removeClassName(this.options.hoverClassName);
    if (this._saving) return;
    this.triggerCallback('onLeaveHover');
  },
  loadExternalText: function() {
    this._form.addClassName(this.options.loadingClassName);
    this._controls.editor.disabled = true;
    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
    Object.extend(options, {
      parameters: 'editorId=' + encodeURIComponent(this.element.id),
      onComplete: Prototype.emptyFunction,
      onSuccess: function(transport) {
        this._form.removeClassName(this.options.loadingClassName);
        var text = transport.responseText;
        if (this.options.stripLoadedTextTags)
          text = text.stripTags();
        this._controls.editor.value = text;
        this._controls.editor.disabled = false;
        this.postProcessEditField();
      }.bind(this),
      onFailure: this._boundFailureHandler
    });
    new Ajax.Request(this.options.loadTextURL, options);
  },
  postProcessEditField: function() {
    var fpc = this.options.fieldPostCreation;
    if (fpc)
      $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
  },
  prepareOptions: function() {
    this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
    Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
    [this._extraDefaultOptions].flatten().compact().each(function(defs) {
      Object.extend(this.options, defs);
    }.bind(this));
  },
  prepareSubmission: function() {
    this._saving = true;
    this.removeForm();
    this.leaveHover();
    this.showSaving();
  },
  registerListeners: function() {
    this._listeners = { };
    var listener;
    $H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
      listener = this[pair.value].bind(this);
      this._listeners[pair.key] = listener;
      if (!this.options.externalControlOnly)
        this.element.observe(pair.key, listener);
      if (this.options.externalControl)
        this.options.externalControl.observe(pair.key, listener);
    }.bind(this));
  },
  removeForm: function() {
    if (!this._form) return;
    this._form.remove();
    this._form = null;
    this._controls = { };
  },
  showSaving: function() {
    this._oldInnerHTML = this.element.innerHTML;
    this.element.innerHTML = this.options.savingText;
    this.element.addClassName(this.options.savingClassName);
    this.element.style.backgroundColor = this._originalBackground;
    this.element.show();
  },
  triggerCallback: function(cbName, arg) {
    if ('function' == typeof this.options[cbName]) {
      this.options[cbName](this, arg);
    }
  },
  unregisterListeners: function() {
    $H(this._listeners).each(function(pair) {
      if (!this.options.externalControlOnly)
        this.element.stopObserving(pair.key, pair.value);
      if (this.options.externalControl)
        this.options.externalControl.stopObserving(pair.key, pair.value);
    }.bind(this));
  },
  wrapUp: function(transport) {
    this.leaveEditMode();
    // Can't use triggerCallback due to backward compatibility: requires
    // binding + direct element
    this._boundComplete(transport, this.element);
  }
});

Object.extend(Ajax.InPlaceEditor.prototype, {
  dispose: Ajax.InPlaceEditor.prototype.destroy
});

Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
  initialize: function($super, element, url, options) {
    this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
    $super(element, url, options);
  },

  createEditField: function() {
    var list = document.createElement('select');
    list.name = this.options.paramName;
    list.size = 1;
    this._controls.editor = list;
    this._collection = this.options.collection || [];
    if (this.options.loadCollectionURL)
      this.loadCollection();
    else
      this.checkForExternalText();
    this._form.appendChild(this._controls.editor);
  },

  loadCollection: function() {
    this._form.addClassName(this.options.loadingClassName);
    this.showLoadingText(this.options.loadingCollectionText);
    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
    Object.extend(options, {
      parameters: 'editorId=' + encodeURIComponent(this.element.id),
      onComplete: Prototype.emptyFunction,
      onSuccess: function(transport) {
        var js = transport.responseText.strip();
        if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
          throw 'Server returned an invalid collection representation.';
        this._collection = eval(js);
        this.checkForExternalText();
      }.bind(this),
      onFailure: this.onFailure
    });
    new Ajax.Request(this.options.loadCollectionURL, options);
  },

  showLoadingText: function(text) {
    this._controls.editor.disabled = true;
    var tempOption = this._controls.editor.firstChild;
    if (!tempOption) {
      tempOption = document.createElement('option');
      tempOption.value = '';
      this._controls.editor.appendChild(tempOption);
      tempOption.selected = true;
    }
    tempOption.update((text || '').stripScripts().stripTags());
  },

  checkForExternalText: function() {
    this._text = this.getText();
    if (this.options.loadTextURL)
      this.loadExternalText();
    else
      this.buildOptionList();
  },

  loadExternalText: function() {
    this.showLoadingText(this.options.loadingText);
    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
    Object.extend(options, {
      parameters: 'editorId=' + encodeURIComponent(this.element.id),
      onComplete: Prototype.emptyFunction,
      onSuccess: function(transport) {
        this._text = transport.responseText.strip();
        this.buildOptionList();
      }.bind(this),
      onFailure: this.onFailure
    });
    new Ajax.Request(this.options.loadTextURL, options);
  },

  buildOptionList: function() {
    this._form.removeClassName(this.options.loadingClassName);
    this._collection = this._collection.map(function(entry) {
      return 2 === entry.length ? entry : [entry, entry].flatten();
    });
    var marker = ('value' in this.options) ? this.options.value : this._text;
    var textFound = this._collection.any(function(entry) {
      return entry[0] == marker;
    }.bind(this));
    this._controls.editor.update('');
    var option;
    this._collection.each(function(entry, index) {
      option = document.createElement('option');
      option.value = entry[0];
      option.selected = textFound ? entry[0] == marker : 0 == index;
      option.appendChild(document.createTextNode(entry[1]));
      this._controls.editor.appendChild(option);
    }.bind(this));
    this._controls.editor.disabled = false;
    Field.scrollFreeActivate(this._controls.editor);
  }
});

//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
//**** This only  exists for a while,  in order to  let ****
//**** users adapt to  the new API.  Read up on the new ****
//**** API and convert your code to it ASAP!            ****

Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
  if (!options) return;
  function fallback(name, expr) {
    if (name in options || expr === undefined) return;
    options[name] = expr;
  };
  fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
    options.cancelLink == options.cancelButton == false ? false : undefined)));
  fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
    options.okLink == options.okButton == false ? false : undefined)));
  fallback('highlightColor', options.highlightcolor);
  fallback('highlightEndColor', options.highlightendcolor);
};

Object.extend(Ajax.InPlaceEditor, {
  DefaultOptions: {
    ajaxOptions: { },
    autoRows: 3,                                // Use when multi-line w/ rows == 1
    cancelControl: 'link',                      // 'link'|'button'|false
    cancelText: 'cancel',
    clickToEditText: 'Click to edit',
    externalControl: null,                      // id|elt
    externalControlOnly: false,
    fieldPostCreation: 'activate',              // 'activate'|'focus'|false
    formClassName: 'inplaceeditor-form',
    formId: null,                               // id|elt
    highlightColor: '#ffff99',
    highlightEndColor: '#ffffff',
    hoverClassName: '',
    htmlResponse: true,
    loadingClassName: 'inplaceeditor-loading',
    loadingText: 'Loading...',
    okControl: 'button',                        // 'link'|'button'|false
    okText: 'ok',
    paramName: 'value',
    rows: 1,                                    // If 1 and multi-line, uses autoRows
    savingClassName: 'inplaceeditor-saving',
    savingText: 'Saving...',
    size: 0,
    stripLoadedTextTags: false,
    submitOnBlur: false,
    textAfterControls: '',
    textBeforeControls: '',
    textBetweenControls: ''
  },
  DefaultCallbacks: {
    callback: function(form) {
      return Form.serialize(form);
    },
    onComplete: function(transport, element) {
      // For backward compatibility, this one is bound to the IPE, and passes
      // the element directly.  It was too often customized, so we don't break it.
      new Effect.Highlight(element, {
        startcolor: this.options.highlightColor, keepBackgroundImage: true });
    },
    onEnterEditMode: null,
    onEnterHover: function(ipe) {
      ipe.element.style.backgroundColor = ipe.options.highlightColor;
      if (ipe._effect)
        ipe._effect.cancel();
    },
    onFailure: function(transport, ipe) {
      alert('Error communication with the server: ' + transport.responseText.stripTags());
    },
    onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
    onLeaveEditMode: null,
    onLeaveHover: function(ipe) {
      ipe._effect = new Effect.Highlight(ipe.element, {
        startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
        restorecolor: ipe._originalBackground, keepBackgroundImage: true
      });
    }
  },
  Listeners: {
    click: 'enterEditMode',
    keydown: 'checkForEscapeOrReturn',
    mouseover: 'enterHover',
    mouseout: 'leaveHover'
  }
});

Ajax.InPlaceCollectionEditor.DefaultOptions = {
  loadingCollectionText: 'Loading options...'
};

// Delayed observer, like Form.Element.Observer, 
// but waits for delay after last key input
// Ideal for live-search fields

Form.Element.DelayedObserver = Class.create({
  initialize: function(element, delay, callback) {
    this.delay     = delay || 0.5;
    this.element   = $(element);
    this.callback  = callback;
    this.timer     = null;
    this.lastValue = $F(this.element); 
    Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
  },
  delayedListener: function(event) {
    if(this.lastValue == $F(this.element)) return;
    if(this.timer) clearTimeout(this.timer);
    this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
    this.lastValue = $F(this.element);
  },
  onTimerEvent: function() {
    this.timer = null;
    this.callback(this.element, $F(this.element));
  }
});


function jiveToggleTab(thisPanel, otherPanels) {
    var thisPanelElem = $(thisPanel);
    // load the tabs for the panel elements
    if (thisPanelElem) {
        var thisTabElem = $(thisPanelElem.id + "-tab");
    }

    // toggle the tab styles and the panel visibility
    if (thisPanelElem && thisPanelElem.style.display == 'none') {
        if (thisTabElem) {
            thisTabElem.className = "jive-body-tab jive-body-tabcurrent";
            thisPanelElem.style.display = "block";
        }
        for (var i = 0; i < otherPanels.length; i++) {
            var thatPanelElem = $(otherPanels[i]);
            if (thatPanelElem) {
                var thatTabElem = $(thatPanelElem.id + "-tab");
            }
            if (thatTabElem) {
                thatTabElem.className = "jive-body-tab";
                thatPanelElem.style.display = "none";
            }
        }
    }
}

/*
jiveToggleOptions function
Function for toggling the state of an options element.
*/
function jiveToggleOptions(optionName) {
    if ($(optionName + '-form').style.display != 'none') {
        Element.hide(optionName + '-form');
        $(optionName + '-hdr').className = 'jive-compose-hdr-opt-closed';
    }
    else
    {
        $(optionName + '-form').style.display = 'block';
        $(optionName + '-hdr').className = 'jive-compose-hdr-opt';
    }
}

function jiveShowTopicFilter(thisID) {
    if ($(thisID).style.display != 'none') {
        Element.toggle($(thisID));
		//Effect.toggle($(thisID), 'slide', {duration: .4});
    }
    else
    {
        Element.toggle($(thisID));
		//Effect.toggle($(thisID), 'slide', {duration: .4});
    }
}

function jiveToggleSpaceDetails(thisID) {
    Element.toggle($(thisID));

    if ($(thisID).style.display != 'none') {
        $(thisID + '-less').style.display = '';
        $(thisID + '-more').style.display = 'none';
    }
    else
    {
        $(thisID + '-more').style.display = '';
        $(thisID + '-less').style.display = 'none';
    }
}

function jiveToggleSpaceDetails2(thisID) {
    if ($(thisID).className == 'jive-space-namedesc-full') {
        $(thisID).className = '';
        $(thisID + '-more').style.display = '';
        $(thisID + '-less').style.display = 'none';
    }
    else
    {
        $(thisID).className = 'jive-space-namedesc-full';
        $(thisID + '-less').style.display = '';
        $(thisID + '-more').style.display = 'none';
    }
}

function callOnLoad(init) {
    if (window.addEventListener) {
        window.addEventListener("load", init, false);
    }
    else if (window.attachEvent) {
        window.attachEvent("onload", init);
    }
    else
    {
        window.onload = init;
    }
}

Jive = Class.create();
Jive.AlertMessage = function(element) {
    var options = arguments[1] || {} ;

    new Effect.Appear(element, {
        queue: 'front',
        scope: element,
        duration: 1,
        beforeStart:options.beforeStart,
        afterFinish:function(obj) {
            new Pause(1);
            new Effect.Fade(element, {
                queue: 'end',
                scope: obj,
                duration: 1,
                afterFinish:options.afterFinish
            });
        }
    });
};

function Pause(duration, busy) {
    this.duration = duration * 1000;
    this.busywork = null; // function to call while waiting.
    this.runner = 0;

    if (arguments.length == 2) {
        this.busywork = busy;
    }

    this.pause(this.duration);

}

Pause.prototype.pause = function(duration) {
    if ((duration == null) || (duration < 0)) {
        return;
    }

    var later = (new Date()).getTime() + duration;

    while (true) {
        if ((new Date()).getTime() > later) {
            break;
        }

        this.runner++;

        if (this.busywork != null) {
            this.busywork(this.runner);
        }

    } // while

} // pause method

var TimeoutExecutor = Class.create();
TimeoutExecutor.prototype = {
    initialize: function(callback, timeout) {
        this.callback = callback;
        this.timeout = timeout;
        this.currentlyExecuting = false;
        this.registerCallback();
    },
    registerCallback: function() {
        this.timeoutID = setTimeout(this.onTimerEvent.bind(this), this.timeout);
    },
    onTimerEvent: function() {
        try {
            this.currentlyExecuting = true;
            if (this.callback && this.callback instanceof Function) {
                this.callback();
            }
        }
        finally {
            this.currentlyExecuting = false;
            delete this.timeoutID;
        }
    },
    cancel: function() {
        if (!this.currentlyExecuting && this.timeoutID) {
            clearTimeout(this.timeoutID);
            delete this.timeoutID;
        }
    },
    reset: function() {
        if (!this.currentlyExecuting && this.timeoutID) {
            clearTimeout(this.timeoutID);
            delete this.timeoutID;
            this.registerCallback();
        }
    }
}

var QuickUserProfile = Class.create();
QuickUserProfile.prototype = {

/*
* Initialize the QuickUserProfile object.
*/
    initialize: function(userTT, userTTURL, textTTLoading, textTTError)
    {
        this.loadingContent = '<strong class="jive-tooltip2-loading">' + textTTLoading + '</strong>';
        this.userTT = userTT;
        this.userTTURL = userTTURL.indexOf('?') < 0? userTTURL + '?tooltip=true' : userTTURL + '&tooltip=true';
        this.textErrorTT = textTTError;
        this.jiveUserTips = new SuperNote('jiveTT', {showDelay: 700, hideDelay: 30, cssProp: 'visibility', cssVis: 'visible', cssHid: 'hidden'});
    },

    getUserProfileTooltip: function(userID) {
        this.cancelTooltip();
        $(this.userTT).innerHTML = this.loadingContent;
        this.timeoutExecutor = new TimeoutExecutor(this.getUserProfile.bind(this, userID), 700);    
    },

    getUserProfile: function(userID) {
        var instance = this;
        new Ajax.Updater( this.userTT, this.userTTURL, {
            method: 'get',
            parameters: {
                'targetUser': userID
            },
            onError: function() {
                $(this.userTT).innerHTML = instance.textTTError;
            }   
        });
    },

    cancelTooltip: function() {
        if (this.timeoutExecutor) {
            this.timeoutExecutor.cancel();
        }
    }
}


/*
 *
 * Copyright (c) 2004-2005 by Zapatec, Inc.
 * http://www.zapatec.com
 * 1700 MLK Way, Berkeley, California,
 * 94709, U.S.A.
 * All rights reserved.
 *
 *
 */


if(typeof Zapatec=='undefined'){Zapatec=function(){};}
Zapatec.version='07-01';if(typeof Zapatec.zapatecPath=='undefined'){Zapatec.zapatecPath=function(){if(document.documentElement){var aTokens=document.documentElement.innerHTML.match(/<script[^>]+src="([^"]*zapatec(-core|-src)?.js[^"]*)"/i);if(aTokens&&aTokens.length>=2){aTokens=aTokens[1].split('?');aTokens=aTokens[0].split('/');if(Array.prototype.pop){aTokens.pop();}else{aTokens.length-=1;}
return aTokens.length?aTokens.join('/')+'/':'';}}
return'';}();}
if(typeof Zapatec=='undefined'){Zapatec=function(){};}
Zapatec.Utils={};Zapatec.Utils.getAbsolutePos=function(el,scrollOff){var SL=0,ST=0;if(!scrollOff){var is_div=/^div$/i.test(el.tagName);if(is_div&&el.scrollLeft)
SL=el.scrollLeft;if(is_div&&el.scrollTop)
ST=el.scrollTop;}
var r={x:el.offsetLeft-SL,y:el.offsetTop-ST};if(el.offsetParent){var tmp=this.getAbsolutePos(el.offsetParent);r.x+=tmp.x;r.y+=tmp.y;}
return r;};Zapatec.Utils.getElementOffset=function(oEl){var iLeft=iTop=iWidth=iHeight=0;if(oEl.getBoundingClientRect){var oRect=oEl.getBoundingClientRect();iLeft=oRect.left;iTop=oRect.top;iWidth=oRect.right-iLeft;iHeight=oRect.bottom-iTop;iLeft+=Zapatec.Utils.getPageScrollX()-2;iTop+=Zapatec.Utils.getPageScrollY()-2;}else{iWidth=oEl.offsetWidth;iHeight=oEl.offsetHeight;var sPos=Zapatec.Utils.getStyleProperty(oEl,'position');if(sPos=='fixed'){iLeft=oEl.offsetLeft+Zapatec.Utils.getPageScrollX();iTop=oEl.offsetTop+Zapatec.Utils.getPageScrollY();}else if(sPos=='absolute'){while(oEl){var sTag=oEl.tagName;if(sTag){sTag=sTag.toLowerCase();if(sTag!='body'&&sTag!='html'){iLeft+=parseInt(oEl.offsetLeft,10)||0;iTop+=parseInt(oEl.offsetTop,10)||0;}}
oEl=oEl.offsetParent;var sTag=oEl?oEl.tagName:null;if(sTag){sTag=sTag.toLowerCase();if(sTag!='body'&&sTag!='html'){iLeft-=oEl.scrollLeft;iTop-=oEl.scrollTop;}}}}else{var oP=oEl;while(oP){iLeft+=parseInt(oP.offsetLeft,10)||0;iTop+=parseInt(oP.offsetTop,10)||0;oP=oP.offsetParent;}
oP=oEl;while(oP.parentNode){oP=oP.parentNode;var sTag=oP.tagName;if(sTag){sTag=sTag.toLowerCase();if(sTag!='body'&&sTag!='html'&&sTag!='tr'){iLeft-=oP.scrollLeft;iTop-=oP.scrollTop;}}}}}
return{left:iLeft,top:iTop,x:iLeft,y:iTop,width:iWidth,height:iHeight};};Zapatec.Utils.getElementOffsetScrollable=function(oEl){var oPos=Zapatec.Utils.getElementOffset(oEl);if(oEl.scrollLeft){oPos.left-=oEl.scrollLeft;oPos.x=oPos.left;}
if(oEl.scrollTop){oPos.top-=oEl.scrollTop;oPos.y=oPos.top;}
return oPos;};Zapatec.Utils.fixBoxPosition=function(box,leave){var screenX=Zapatec.Utils.getPageScrollX();var screenY=Zapatec.Utils.getPageScrollY();var sizes=Zapatec.Utils.getWindowSize();leave=parseInt(leave,10)||0;if(box.x<screenX){box.x=screenX+leave;}
if(box.y<screenY){box.y=screenY+leave;}
if(box.x+box.width>screenX+sizes.width){box.x=screenX+sizes.width-box.width-leave;}
if(box.y+box.height>screenY+sizes.height){box.y=screenY+sizes.height-box.height-leave;}};Zapatec.Utils.isRelated=function(el,evt){evt||(evt=window.event);var related=evt.relatedTarget;if(!related){var type=evt.type;if(type=="mouseover"){related=evt.fromElement;}else if(type=="mouseout"){related=evt.toElement;}}
try{while(related){if(related==el){return true;}
related=related.parentNode;}}catch(e){};return false;};Zapatec.Utils.removeClass=function(el,className){if(!(el&&el.className)){return;}
var cls=el.className.split(" ");var ar=[];for(var i=cls.length;i>0;){if(cls[--i]!=className){ar[ar.length]=cls[i];}}
el.className=ar.join(" ");};Zapatec.Utils.addClass=function(el,className){Zapatec.Utils.removeClass(el,className);el.className+=" "+className;};Zapatec.Utils.getElement=function(ev){if(Zapatec.is_ie){return window.event.srcElement;}else{return ev.currentTarget;}};Zapatec.Utils.getTargetElement=function(ev){if(Zapatec.is_ie){return window.event.srcElement;}else{return ev.target;}};Zapatec.Utils.getMousePos=function(oEv){oEv||(oEv=window.event);var oPos={pageX:0,pageY:0,clientX:0,clientY:0};if(oEv){var bIsPageX=(typeof oEv.pageX!='undefined');var bIsClientX=(typeof oEv.clientX!='undefined');if(bIsPageX||bIsClientX){if(bIsPageX){oPos.pageX=oEv.pageX;oPos.pageY=oEv.pageY;}else{oPos.pageX=oEv.clientX+Zapatec.Utils.getPageScrollX();oPos.pageY=oEv.clientY+Zapatec.Utils.getPageScrollY();}
if(bIsClientX){oPos.clientX=oEv.clientX;oPos.clientY=oEv.clientY;}else{oPos.clientX=oEv.pageX-Zapatec.Utils.getPageScrollX();oPos.clientY=oEv.pageY-Zapatec.Utils.getPageScrollY();}}}
return oPos;};Zapatec.Utils.stopEvent=function(ev){ev||(ev=window.event);if(ev){if(Zapatec.is_ie){ev.cancelBubble=true;ev.returnValue=false;}else{ev.preventDefault();ev.stopPropagation();}}
return false;};Zapatec.Utils.removeOnUnload=[];Zapatec.Utils.addEvent=function(oElement,sEvent,fListener,bUseCapture){if(oElement.addEventListener){if(!bUseCapture){bUseCapture=false;}
oElement.addEventListener(sEvent,fListener,bUseCapture);}else if(oElement.attachEvent){oElement.detachEvent('on'+sEvent,fListener);oElement.attachEvent('on'+sEvent,fListener);if(bUseCapture){oElement.setCapture(false);}}
Zapatec.Utils.removeOnUnload.push({'element':oElement,'event':sEvent,'listener':fListener,'capture':bUseCapture});};Zapatec.Utils.removeEvent=function(oElement,sEvent,fListener,bUseCapture){if(oElement.removeEventListener){oElement.removeEventListener(sEvent,fListener,bUseCapture);}else if(oElement.detachEvent){oElement.detachEvent('on'+sEvent,fListener);}
for(var iLis=Zapatec.Utils.removeOnUnload.length-1;iLis>=0;iLis--){var oParams=Zapatec.Utils.removeOnUnload[iLis];if(!oParams){continue;}
if(oElement==oParams['element']&&sEvent==oParams['event']&&fListener==oParams['listener']&&bUseCapture==oParams['capture']){Zapatec.Utils.removeOnUnload[iLis]=null;Zapatec.Utils.removeEvent(oParams['element'],oParams['event'],oParams['listener'],oParams['capture']);}}};Zapatec.Utils.createElement=function(type,parent,selectable){var el=null;if(window.self.document.createElementNS)
el=window.self.document.createElementNS("http://www.w3.org/1999/xhtml",type);else
el=document.createElement(type);if(typeof parent!="undefined"&&parent!=null)
parent.appendChild(el);if(!selectable){if(Zapatec.is_ie)
el.setAttribute("unselectable",true);if(Zapatec.is_gecko)
el.style.setProperty("-moz-user-select","none","");}
return el;};Zapatec.Utils.writeCookie=function(name,value,domain,path,exp_days){value=escape(value);var ck=name+"="+value,exp;if(domain)
ck+=";domain="+domain;if(path)
ck+=";path="+path;if(exp_days){exp=new Date();exp.setTime(exp_days*86400000+exp.getTime());ck+=";expires="+exp.toGMTString();}
document.cookie=ck;};Zapatec.Utils.getCookie=function(name){var pattern=name+"=";var tokenPos=0;while(tokenPos<document.cookie.length){var valuePos=tokenPos+pattern.length;if(document.cookie.substring(tokenPos,valuePos)==pattern){var endValuePos=document.cookie.indexOf(";",valuePos);if(endValuePos==-1){endValuePos=document.cookie.length;}
return unescape(document.cookie.substring(valuePos,endValuePos));}
tokenPos=document.cookie.indexOf(" ",tokenPos)+1;if(tokenPos==0){break;}}
return null;};Zapatec.Utils.makePref=function(obj){function stringify(val){if(typeof val=="object"&&!val)
return"null";else if(typeof val=="number"||typeof val=="boolean")
return val;else if(typeof val=="string")
return'"'+val.replace(/\x22/,"\\22")+'"';else return null;};var txt="",i;for(i in obj)
txt+=(txt?",'":"'")+i+"':"+stringify(obj[i]);return txt;};Zapatec.Utils.loadPref=function(sCookie){var oCookie=zapatecTransport.parseJson({strJson:'{'+sCookie+'}'});if(!oCookie||typeof oCookie!='object'){oCookie={};}return oCookie;};Zapatec.Utils.mergeObjects=function(dest,src){for(var i in src)
dest[i]=src[i];};Zapatec.Utils.__wch_id=0;Zapatec.Utils.createWCH=function(element){var f=null;element=element||document.body;if(Zapatec.is_ie&&!Zapatec.is_ie5){var filter='filter:progid:DXImageTransform.Microsoft.alpha(style=0,opacity=0);';var id="WCH"+(++Zapatec.Utils.__wch_id);element.insertAdjacentHTML
('beforeEnd','<iframe id="'+id+'" scrolling="no" frameborder="0" '+'style="z-index:0;position:absolute;visibility:hidden;'+filter+'border:0;top:0;left:0;width:0;height:0" '+'src="javascript:false"></iframe>');f=document.getElementById(id);}
return f;};Zapatec.Utils.setupWCH_el=function(f,el,el2){if(f){var pos=Zapatec.Utils.getAbsolutePos(el),X1=pos.x,Y1=pos.y,X2=X1+el.offsetWidth,Y2=Y1+el.offsetHeight;if(el2){var p2=Zapatec.Utils.getAbsolutePos(el2),XX1=p2.x,YY1=p2.y,XX2=XX1+el2.offsetWidth,YY2=YY1+el2.offsetHeight;if(X1>XX1)
X1=XX1;if(Y1>YY1)
Y1=YY1;if(X2<XX2)
X2=XX2;if(Y2<YY2)
Y2=YY2;}
Zapatec.Utils.setupWCH(f,X1,Y1,X2-X1,Y2-Y1);}};Zapatec.Utils.setupWCH=function(f,x,y,w,h){if(f){var s=f.style;(typeof x!="undefined")&&(s.left=x+"px");(typeof y!="undefined")&&(s.top=y+"px");(typeof w!="undefined")&&(s.width=w+"px");(typeof h!="undefined")&&(s.height=h+"px");s.visibility="inherit";}};Zapatec.Utils.hideWCH=function(f){if(f)
f.style.visibility="hidden";};Zapatec.Utils.getPageScrollY=function(){if(window.pageYOffset){return window.pageYOffset;}else if(document.body&&document.body.scrollTop){return document.body.scrollTop;}else if(document.documentElement&&document.documentElement.scrollTop){return document.documentElement.scrollTop;}
return 0;};Zapatec.Utils.getPageScrollX=function(){if(window.pageXOffset){return window.pageXOffset;}else if(document.body&&document.body.scrollLeft){return document.body.scrollLeft;}else if(document.documentElement&&document.documentElement.scrollLeft){return document.documentElement.scrollLeft;}
return 0;};Zapatec.ScrollWithWindow={};Zapatec.ScrollWithWindow.list=[];Zapatec.ScrollWithWindow.stickiness=0.25;Zapatec.ScrollWithWindow.register=function(oElement){var iTop=oElement.offsetTop||0;var iLeft=oElement.offsetLeft||0;Zapatec.ScrollWithWindow.list.push({node:oElement,origTop:iTop,origLeft:iLeft});if(!Zapatec.ScrollWithWindow.interval){Zapatec.ScrollWithWindow.on();}};Zapatec.ScrollWithWindow.unregister=function(oElement){for(var iItem=0;iItem<Zapatec.ScrollWithWindow.list.length;iItem++){var oItem=Zapatec.ScrollWithWindow.list[iItem];if(oElement==oItem.node){Zapatec.ScrollWithWindow.list.splice(iItem,1);if(!Zapatec.ScrollWithWindow.list.length){Zapatec.ScrollWithWindow.off();}
return;}}};Zapatec.ScrollWithWindow.moveTop=function(iTop){Zapatec.ScrollWithWindow.top+=(iTop-Zapatec.ScrollWithWindow.top)*Zapatec.ScrollWithWindow.stickiness;if(Math.abs(Zapatec.ScrollWithWindow.top-iTop)<=1){Zapatec.ScrollWithWindow.top=iTop;}
for(var iItem=0;iItem<Zapatec.ScrollWithWindow.list.length;iItem++){var oItem=Zapatec.ScrollWithWindow.list[iItem];var oElement=oItem.node;oElement.style.position='absolute';if(!oItem.origTop&&oItem.origTop!==0){oItem.origTop=parseInt(oElement.style.top)||0;}
oElement.style.top=oItem.origTop+
parseInt(Zapatec.ScrollWithWindow.top)+'px';}};Zapatec.ScrollWithWindow.moveLeft=function(iLeft){Zapatec.ScrollWithWindow.left+=(iLeft-Zapatec.ScrollWithWindow.left)*Zapatec.ScrollWithWindow.stickiness;if(Math.abs(Zapatec.ScrollWithWindow.left-iLeft)<=1){Zapatec.ScrollWithWindow.left=iLeft;}
for(var iItem=0;iItem<Zapatec.ScrollWithWindow.list.length;iItem++){var oItem=Zapatec.ScrollWithWindow.list[iItem];var oElement=oItem.node;oElement.style.position='absolute';if(!oItem.origLeft&&oItem.origLeft!==0){oItem.origLeft=parseInt(oElement.style.left)||0;}
oElement.style.left=oItem.origLeft+
parseInt(Zapatec.ScrollWithWindow.left)+'px';}};Zapatec.ScrollWithWindow.cycle=function(){var iTop=Zapatec.Utils.getPageScrollY();var iLeft=Zapatec.Utils.getPageScrollX();if(iTop!=Zapatec.ScrollWithWindow.top){Zapatec.ScrollWithWindow.moveTop(iTop);}
if(iLeft!=Zapatec.ScrollWithWindow.left){Zapatec.ScrollWithWindow.moveLeft(iLeft);}};Zapatec.ScrollWithWindow.on=function(){if(Zapatec.ScrollWithWindow.interval){return;}
Zapatec.ScrollWithWindow.top=Zapatec.Utils.getPageScrollY();Zapatec.ScrollWithWindow.left=Zapatec.Utils.getPageScrollX();Zapatec.ScrollWithWindow.interval=setInterval(Zapatec.ScrollWithWindow.cycle,50);};Zapatec.ScrollWithWindow.off=function(){if(!Zapatec.ScrollWithWindow.interval){return;}
clearInterval(Zapatec.ScrollWithWindow.interval);Zapatec.ScrollWithWindow.interval=null;};Zapatec.FixateOnScreen={};Zapatec.FixateOnScreen.getExpression=function(coord,direction){return"Zapatec.Utils.getPageScroll"+direction.toUpperCase()+"() + "+coord;};Zapatec.FixateOnScreen.parseCoordinates=function(element){if(!this.isRegistered(element)){return false;}
var x=0;var y=0;var style=element.style;if(Zapatec.is_ie&&!Zapatec.is_ie7){x=style.getExpression("left").split(" ");x=parseInt(x[x.length-1],10);y=style.getExpression("top").split(" ");y=parseInt(y[y.length-1],10);}else{x=parseInt(style.left,10);y=parseInt(style.top,10);}
x+=Zapatec.Utils.getPageScrollX();y+=Zapatec.Utils.getPageScrollY();return{x:x,y:y};};Zapatec.FixateOnScreen.correctCoordinates=function(x,y){position={x:x,y:y};if(position.x||position.x===0){position.x-=Zapatec.Utils.getPageScrollX();if(Zapatec.is_ie&&!Zapatec.is_ie7){position.x=this.getExpression(position.x,"X");;}else{position.x+="px";}}
if(position.y||position.y===0){position.y-=Zapatec.Utils.getPageScrollY();if(Zapatec.is_ie&&!Zapatec.is_ie7){position.y=this.getExpression(position.y,"Y");;}else{position.y+="px";}}
return position;};Zapatec.FixateOnScreen.register=function(element){if(!Zapatec.isHtmlElement(element)){return false;}
if(this.isRegistered(element)){return true;}
var pos=Zapatec.Utils.getElementOffset(element);pos={x:parseInt(element.style.left,10)||pos.x,y:parseInt(element.style.top,10)||pos.y}
pos=this.correctCoordinates(pos.x,pos.y);if(!Zapatec.is_ie||Zapatec.is_ie7){var restorer=element.restorer;if(!restorer||!restorer.getObject||restorer.getObject()!=element){restorer=element.restorer=new Zapatec.SRProp(element);}
restorer.saveProp("style.position");element.style.position="fixed";element.style.left=pos.x;element.style.top=pos.y;}else{element.style.setExpression("left",pos.x);element.style.setExpression("top",pos.y);}
element.zpFixed=true;return true;};Zapatec.FixateOnScreen.unregister=function(element){if(!Zapatec.isHtmlElement(element)){return false;}
var pos=this.parseCoordinates(element);if(pos===false){return true;}
if(Zapatec.is_ie&&!Zapatec.is_ie7){element.style.removeExpression("left");element.style.removeExpression("top");}
element.style.left=pos.x+"px";element.style.top=pos.y+"px";if(!Zapatec.is_ie||Zapatec.is_ie7){element.restorer.restoreProp("style.position",true);}
element.zpFixed=false;return true;};Zapatec.FixateOnScreen.isRegistered=function(element){if(element.zpFixed){return true;}
return false;};Zapatec.Utils.destroy=function(el){if(el&&el.parentNode)
el.parentNode.removeChild(el);};Zapatec.Utils.newCenteredWindow=function(url,windowName,width,height,scrollbars){var leftPosition=0;var topPosition=0;if(screen.width)
leftPosition=(screen.width-width)/2;if(screen.height)
topPosition=(screen.height-height)/2;var winArgs='height='+height+',width='+width+',top='+topPosition+',left='+leftPosition+',scrollbars='+scrollbars+',resizable';var win=window.open(url,windowName,winArgs);return win;};Zapatec.Utils.getWindowSize=function(){var iWidth=0;var iHeight=0;if(Zapatec.is_opera){iWidth=document.body.clientWidth||0;iHeight=document.body.clientHeight||0;}else if(Zapatec.is_khtml){iWidth=window.innerWidth||0;iHeight=window.innerHeight||0;}else if(document.compatMode&&document.compatMode=='CSS1Compat'){iWidth=document.documentElement.clientWidth||0;iHeight=document.documentElement.clientHeight||0;}else{iWidth=document.body.clientWidth||0;iHeight=document.body.clientHeight||0;}
return{width:iWidth,height:iHeight};};Zapatec.Utils.selectOption=function(sel,val,call_default){var a=sel.options,i,o;for(i=a.length;--i>=0;){o=a[i];o.selected=(o.value==val);}
sel.value=val;if(call_default){if(typeof sel.onchange=="function")
sel.onchange();else if(typeof sel.onchange=="string")
eval(sel.onchange);}};Zapatec.Utils.getNextSibling=function(el,tag,alternateTag){el=el.nextSibling;if(!tag){return el;}
tag=tag.toLowerCase();if(alternateTag)alternateTag=alternateTag.toLowerCase();while(el){if(el.nodeType==1&&(el.tagName.toLowerCase()==tag||(alternateTag&&el.tagName.toLowerCase()==alternateTag))){return el;}
el=el.nextSibling;}
return el;};Zapatec.Utils.getPreviousSibling=function(el,tag,alternateTag){el=el.previousSibling;if(!tag){return el;}
tag=tag.toLowerCase();if(alternateTag)alternateTag=alternateTag.toLowerCase();while(el){if(el.nodeType==1&&(el.tagName.toLowerCase()==tag||(alternateTag&&el.tagName.toLowerCase()==alternateTag))){return el;}
el=el.previousSibling;}
return el;};Zapatec.Utils.getFirstChild=function(el,tag,alternateTag){if(!el){return null;}
el=el.firstChild;if(!el){return null;}
if(!tag){return el;}
tag=tag.toLowerCase();if(el.nodeType==1){if(el.tagName.toLowerCase()==tag){return el;}else if(alternateTag){alternateTag=alternateTag.toLowerCase();if(el.tagName.toLowerCase()==alternateTag){return el;}}}
return Zapatec.Utils.getNextSibling(el,tag,alternateTag);};Zapatec.Utils.getLastChild=function(el,tag,alternateTag){if(!el){return null;}
el=el.lastChild;if(!el){return null;}
if(!tag){return el;}
tag=tag.toLowerCase();if(el.nodeType==1){if(el.tagName.toLowerCase()==tag){return el;}else if(alternateTag){alternateTag=alternateTag.toLowerCase();if(el.tagName.toLowerCase()==alternateTag){return el;}}}
return Zapatec.Utils.getPreviousSibling(el,tag,alternateTag);};Zapatec.Utils.getChildText=function(objNode){if(objNode==null){return'';}
var arrText=[];var objChild=objNode.firstChild;while(objChild!=null){if(objChild.nodeType==3){arrText.push(objChild.data);}
objChild=objChild.nextSibling;}
return arrText.join(' ');};Zapatec.Utils.insertAfter=function(oldNode,newNode){if(oldNode.nextSibling){oldNode.parentNode.insertBefore(newNode,oldNode.nextSibling);}else{oldNode.parentNode.appendChild(newNode);}}
Zapatec.Utils._ids={};Zapatec.Utils.generateID=function(code,id){if(typeof id=="undefined"){if(typeof this._ids[code]=="undefined")
this._ids[code]=0;id=++this._ids[code];}
return"zapatec-"+code+"-"+id;};Zapatec.Utils.addTooltip=function(target,tooltip){return new Zapatec.Tooltip({target:target,tooltip:tooltip});};Zapatec.isLite=true;Zapatec.Utils.checkLinks=function(){var anchors=document.getElementsByTagName('A');for(var ii=0;ii<anchors.length;ii++){if(Zapatec.Utils.checkLink(anchors[ii])){return true;}}
return false;}
Zapatec.Utils.checkLink=function(lnk){if(!lnk){return false;}
if(!/^https?:\/\/((dev|www)\.)?zapatec\.com/i.test(lnk.href)){return false;}
var textContent=""
for(var ii=0;ii<lnk.childNodes.length;ii++){if(lnk.childNodes[ii].nodeType==3){textContent+=lnk.childNodes[ii].nodeValue;}}
if(textContent.length<4){return false;}
var parent=lnk;while(parent&&parent.nodeName.toLowerCase()!="html"){if(Zapatec.Utils.getStyleProperty(parent,"display")=="none"||Zapatec.Utils.getStyleProperty(parent,"visibility")=="hidden"||Zapatec.Utils.getStyleProperty(parent,"opacity")=="0"||Zapatec.Utils.getStyleProperty(parent,"-moz-opacity")=="0"||/alpha\(opacity=0\)/i.test(Zapatec.Utils.getStyleProperty(parent,"filter"))){return false;}
parent=parent.parentNode;}
var coords=Zapatec.Utils.getElementOffset(lnk);if(coords.left<0||coords.top<0){return false;}
return true;}
Zapatec.Utils.checkActivation=function(){if(!Zapatec.isLite)return true;var arrProducts=[]
add_product=function(script,webdir_in,name_in)
{arrProducts[script]={webdir:webdir_in,name:name_in,bActive:false}}
add_product('calendar.js','prod1','Calendar')
add_product('zpmenu.js','menu','Menu')
add_product('tree.js','prod3','Tree')
add_product('form.js','forms','Forms')
add_product('effects.js','effects','Effects')
add_product('hoverer.js','effects','Effects - Hoverer')
add_product('slideshow.js','effects','Effects - Slideshow')
add_product('zpgrid.js','grid','Grid')
add_product('slider.js','slider','Slider')
add_product('zptabs.js','tabs','Tabs')
add_product('zptime.js','time','Time')
add_product('window.js','windows','Window')
var strName,arrName,i
var bProduct=false
var scripts=document.getElementsByTagName('script');for(i=0;i<scripts.length;i++)
{if(/wizard.js/i.test(scripts[i].src))
return true
arrName=scripts[i].src.split('/')
if(arrName.length==0)
strName=scripts[i]
else
strName=arrName[arrName.length-1]
strName=strName.toLowerCase()
if(typeof arrProducts[strName]!='undefined')
{bProduct=true
arrProducts[strName].bActive=true}}
if(!bProduct||Zapatec.Utils.checkLinks()){return true;}
var strMsg='You are using the Free version of the Zapatec Software.\n'+'While using the Free version, a link to www.zapatec.com in this page is required.'
for(i in arrProducts)
if(arrProducts[i].bActive==true)
strMsg+='\nTo purchase the Zapatec '+arrProducts[i].name+' visit www.zapatec.com/website/main/products/'+arrProducts[i].webdir+'/'
alert(strMsg)
return false;}
Zapatec.Utils.clone=function(oSource){var oClone;if(!oSource&&typeof oSource=='object'){return null;}else if(typeof oSource=='undefined'){return oClone;}
if((oSource instanceof String)||(oSource instanceof Number)||(oSource instanceof Boolean)){oClone=new oSource.constructor(oSource.valueOf());}else{oClone=new oSource.constructor();}
for(var sProperty in oSource){if(typeof oSource[sProperty]=='object'){oClone[sProperty]=Zapatec.Utils.clone(oSource[sProperty],true);}else{oClone[sProperty]=oSource[sProperty];}}
return oClone;};Zapatec.is_opera=/opera/i.test(navigator.userAgent);Zapatec.is_ie=(/msie/i.test(navigator.userAgent)&&!Zapatec.is_opera);Zapatec.is_ie5=(Zapatec.is_ie&&/msie 5\.0/i.test(navigator.userAgent));Zapatec.is_ie7=(Zapatec.is_ie&&/msie 7\.0/i.test(navigator.userAgent));Zapatec.is_mac_ie=(/msie.*mac/i.test(navigator.userAgent)&&!Zapatec.is_opera);Zapatec.is_khtml=/Konqueror|Safari|KHTML/i.test(navigator.userAgent);Zapatec.is_konqueror=/Konqueror/i.test(navigator.userAgent);Zapatec.is_gecko=/Gecko/i.test(navigator.userAgent);Zapatec.is_webkit=/WebKit/i.test(navigator.userAgent);Zapatec.webkitVersion=Zapatec.is_webkit?parseInt(navigator.userAgent.replace(/.+WebKit\/([0-9]+)\..+/,"$1")):-1;if(!Object.prototype.hasOwnProperty){Object.prototype.hasOwnProperty=function(strProperty){try{var objPrototype=this.constructor.prototype;while(objPrototype){if(objPrototype[strProperty]==this[strProperty]){return false;}
objPrototype=objPrototype.prototype;}}catch(objException){}
return true;};}
if(!Function.prototype.call){Function.prototype.call=function(){var objThis=arguments[0];objThis._this_func=this;var arrArgs=[];for(var iArg=1;iArg<arguments.length;iArg++){arrArgs[arrArgs.length]='arguments['+iArg+']';}
var ret=eval('objThis._this_func('+arrArgs.join(',')+')');objThis._this_func=null;return ret;};}
if(!Function.prototype.apply){Function.prototype.apply=function(){var objThis=arguments[0];var objArgs=arguments[1];objThis._this_func=this;var arrArgs=[];if(objArgs){for(var iArg=0;iArg<objArgs.length;iArg++){arrArgs[arrArgs.length]='objArgs['+iArg+']';}}
var ret=eval('objThis._this_func('+arrArgs.join(',')+')');objThis._this_func=null;return ret;};}
if(!Array.prototype.pop){Array.prototype.pop=function(){var last;if(this.length){last=this[this.length-1];this.length-=1;}
return last;};}
if(!Array.prototype.push){Array.prototype.push=function(){for(var i=0;i<arguments.length;i++){this[this.length]=arguments[i];}
return this.length;};}
if(!Array.prototype.shift){Array.prototype.shift=function(){var first;if(this.length){first=this[0];for(var i=0;i<this.length-1;i++){this[i]=this[i+1];}
this.length-=1;}
return first;};}
if(!Array.prototype.unshift){Array.prototype.unshift=function(){if(arguments.length){var i,len=arguments.length;for(i=this.length+len-1;i>=len;i--){this[i]=this[i-len];}
for(i=0;i<len;i++){this[i]=arguments[i];}}
return this.length;};}
if(!Array.prototype.splice){Array.prototype.splice=function(index,howMany){var elements=[],removed=[],i;for(i=2;i<arguments.length;i++){elements.push(arguments[i]);}
for(i=index;(i<index+howMany)&&(i<this.length);i++){removed.push(this[i]);}
for(i=index+howMany;i<this.length;i++){this[i-howMany]=this[i];}
this.length-=removed.length;for(i=this.length+elements.length-1;i>=index+elements.length;i--){this[i]=this[i-elements.length];}
for(i=0;i<elements.length;i++){this[index+i]=elements[i];}
return removed;};}
Zapatec.Utils.arrIndexOf=function(arr,searchElement,fromIndex){if(Array.prototype.indexOf){return arr.indexOf(searchElement,fromIndex);}
if(!fromIndex){fromIndex=0;}
for(var iElement=fromIndex;iElement<arr.length;iElement++){if(arr[iElement]==searchElement){return iElement;}}
return-1;};Zapatec.Log=function(objArgs){if(!objArgs){return;}
var strMessage=objArgs.description;if(objArgs.severity){strMessage=objArgs.severity+':\n'+strMessage;}
if(objArgs.type!="warning"){alert(strMessage);}};Zapatec.Utils.Array={};Zapatec.Utils.Array.insertBefore=function(arr,el,key,nextKey){var tmp=new Array();for(var i in arr){if(i==nextKey){if(key){tmp[key]=el;}else{tmp.push(el);}}
tmp[i]=arr[i];}
return tmp;}
Zapatec.inherit=function(oSubClass,oSuperClass,oArg){var Inheritance=function(){};Inheritance.prototype=oSuperClass.prototype;oSubClass.prototype=new Inheritance();oSubClass.prototype.constructor=oSubClass;oSubClass.SUPERconstructor=oSuperClass;oSubClass.SUPERclass=oSuperClass.prototype;if(typeof oSuperClass.path!='undefined'){if(oArg&&oArg.keepPath){oSubClass.path=oSuperClass.path;}else{oSubClass.path=Zapatec.getPath(oSubClass.id);}}};Zapatec.getPath=function(sId){var sSrc;if(typeof sId=='string'){var oScript=document.getElementById(sId);if(oScript){sSrc=oScript.getAttribute('src');}}
if(!sSrc){if(typeof Zapatec.lastLoadedModule=='string'){return Zapatec.lastLoadedModule;}
if(document.documentElement){var sHtml=document.documentElement.innerHTML;var aMatch=sHtml.match(/<script[^>]+src=[^>]+>/gi);if(aMatch&&aMatch.length){sHtml=aMatch[aMatch.length-1];aMatch=sHtml.match(/src="([^"]+)/i);if(aMatch&&aMatch.length==2){sSrc=aMatch[1];}}}
if(!sSrc){return'';}}
sSrc=sSrc.replace(/\\/g,'/');var aTokens=sSrc.split('?');aTokens=aTokens[0].split('/');aTokens=aTokens.slice(0,-1);if(!aTokens.length){return'';}
return aTokens.join('/')+'/';};Zapatec.Utils.setWindowEvent=function(oEvent){if(oEvent){window.event=oEvent;}};Zapatec.Utils.emulateWindowEvent=function(aEventNames){if(document.addEventListener){for(var iEvent=0;iEvent<aEventNames.length;iEvent++){document.addEventListener(aEventNames[iEvent],Zapatec.Utils.setWindowEvent,true);}}};Zapatec.windowLoaded=typeof(document.readyState)!='undefined'?(document.readyState=='loaded'||document.readyState=='complete'):document.getElementsByTagName!=null&&typeof(document.getElementsByTagName('body')[0])!='undefined';Zapatec.Utils.addEvent(window,"load",function(){Zapatec.windowLoaded=true;});Zapatec.Utils.warnUnload=function(msg,win){Zapatec.Utils.warnUnloadFlag=true;if(typeof(msg)!="string"){msg="All your changes will be lost.";}
if(typeof(win)=='undefined'){win=window;}
Zapatec.Utils.addEvent(win,'beforeunload',function(ev){if(Zapatec.Utils.warnUnloadFlag!=true){return true;}
if(typeof(ev)=='undefined'){ev=window.event;}
ev.returnValue=msg;return false;});}
Zapatec.Utils.unwarnUnload=function(msg,win){Zapatec.Utils.warnUnloadFlag=false;}
Zapatec.Utils.warnUnloadFlag=false;Zapatec.Utils.getMaxZindex=function(){if(window.opera||Zapatec.is_khtml){return 2147483583;}else if(Zapatec.is_ie){return 2147483647;}else{return 10737418239;}};Zapatec.Utils.correctCssLength=function(val){if(typeof val=='undefined'||(typeof val=='object'&&!val)){return'auto';}
val+='';if(!val.length){return'auto';}
if(/\d$/.test(val)){val+='px';}
return val;};Zapatec.Utils.destroyOnUnload=[];Zapatec.Utils.addDestroyOnUnload=function(objElement,strProperty){Zapatec.Utils.destroyOnUnload.push([objElement,strProperty]);};Zapatec.Utils.createProperty=function(objElement,strProperty,val){objElement[strProperty]=val;Zapatec.Utils.addDestroyOnUnload(objElement,strProperty);};Zapatec.Utils.addEvent(window,'unload',function(){for(var iObj=Zapatec.Utils.destroyOnUnload.length-1;iObj>=0;iObj--){var objDestroy=Zapatec.Utils.destroyOnUnload[iObj];objDestroy[0][objDestroy[1]]=null;objDestroy[0]=null;}
for(var iLis=Zapatec.Utils.removeOnUnload.length-1;iLis>=0;iLis--){var oParams=Zapatec.Utils.removeOnUnload[iLis];if(!oParams){continue;}
Zapatec.Utils.removeOnUnload[iLis]=null;Zapatec.Utils.removeEvent(oParams['element'],oParams['event'],oParams['listener'],oParams['capture']);}});Zapatec.Utils.htmlEncode=function(str){str=str.replace(/&/ig,"&amp;");str=str.replace(/</ig,"&lt;");str=str.replace(/>/ig,"&gt;");str=str.replace(/\x22/ig,"&quot;");return str;};Zapatec.Utils.applyStyle=function(elRef,style){if(typeof(elRef)=='string'){elRef=document.getElementById(elRef);}
if(elRef==null||style==null||elRef.style==null){return null;}
if(Zapatec.is_opera){var pairs=style.split(";");for(var ii=0;ii<pairs.length;ii++){var kv=pairs[ii].split(":");if(!kv[1]){continue;}
var value=kv[1].replace(/^\s*/,'').replace(/\s*$/,'');var key="";for(var jj=0;jj<kv[0].length;jj++){if(kv[0].charAt(jj)=="-"){jj++;if(jj<kv[0].length){key+=kv[0].charAt(jj).toUpperCase();}
continue;}
key+=kv[0].charAt(jj);}
switch(key){case"float":key="cssFloat";break;}
try{elRef.style[key]=value;}catch(e){}}}else{elRef.style.cssText=style;}
return true;}
Zapatec.Utils.getStyleProperty=function(objElement,strProperty){if(document.defaultView&&document.defaultView.getComputedStyle){strProperty=strProperty.replace(/([A-Z])/g,'-$1').toLowerCase();var computedStyle=document.defaultView.getComputedStyle(objElement,'');if(computedStyle){return computedStyle.getPropertyValue(strProperty);}}else if(objElement.currentStyle){return objElement.currentStyle[strProperty];}
return objElement.style[strProperty];};Zapatec.Utils.getPrecision=function(dFloat){return(dFloat+'').replace(/^\d*\.*/,'').length;};Zapatec.Utils.setPrecision=function(dFloat,iPrecision){dFloat*=1;if(dFloat.toFixed){return(dFloat*1).toFixed(iPrecision)*1;}
var iPow=Math.pow(10,iPrecision);return parseInt(dFloat*iPow,10)/iPow;};Zapatec.Utils.setPrecisionString=function(dFloat,iPrecision){var sFloat=Zapatec.Utils.setPrecision(dFloat,iPrecision)+'';var iZeros=iPrecision-Zapatec.Utils.getPrecision(sFloat);for(var iZero=0;iZero<iZeros;iZero++){sFloat+='0';}
return sFloat;};Zapatec.Utils.createNestedHash=function(parent,keys,value){if(parent==null||keys==null){return null;}
var tmp=parent;for(var ii=0;ii<keys.length;ii++){if(typeof(tmp[keys[ii]])=='undefined'){tmp[keys[ii]]={};}
if(ii==keys.length-1&&typeof(value)!='undefined'){tmp[keys[ii]]=value;}
tmp=tmp[keys[ii]];}}
Zapatec.implement=function(classOrObject,interfaceStr){if(typeof interfaceStr!="string"){return false;}
if(typeof classOrObject=="function"){classOrObject=classOrObject.prototype;}
if(!classOrObject||typeof classOrObject!="object"){return false;}
var interfaceObj=window;var objs=interfaceStr.split(".");try{for(var i=0;i<objs.length;++i){interfaceObj=interfaceObj[objs[i]];}}catch(e){return false;}
if(typeof classOrObject.interfaces!="object"){classOrObject.interfaces={};classOrObject.interfaces[interfaceStr]=true;}else if(classOrObject.interfaces[interfaceStr]!==true){classOrObject.interfaces=Zapatec.Utils.clone(classOrObject.interfaces);classOrObject.interfaces[interfaceStr]=true;}else{return true;}
for(var iProp in interfaceObj){classOrObject[iProp]=interfaceObj[iProp];}
classOrObject.hasInterface=function(interfaceStr){if(this.interfaces[interfaceStr]===true){return true;}
return false;}
return true;};Zapatec.Utils.getCharFromEvent=function(evt){if(!evt){evt=window.event;}
var response={};if(Zapatec.is_gecko&&!Zapatec.is_khtml&&evt.type!="keydown"&&evt.type!="keyup"){if(evt.charCode){response.chr=String.fromCharCode(evt.charCode);}else{response.charCode=evt.keyCode;}}else{response.charCode=evt.keyCode||evt.which;response.chr=String.fromCharCode(response.charCode);}
if(Zapatec.is_opera&&response.charCode==0){response.charCode=null;response.chr=null;}
if(Zapatec.is_khtml&&response.charCode==63272){response.charCode=46;response.chr=null;}
return response;}
if(typeof Zapatec=='undefined'){Zapatec=function(){};}
Zapatec.Transport=function(){};if(typeof ActiveXObject!='undefined'){Zapatec.Transport.XMLDOM=null;Zapatec.Transport.XMLHTTP=null;Zapatec.Transport.pickActiveXVersion=function(aVersions){for(var iVn=0;iVn<aVersions.length;iVn++){try{var oDoc=new ActiveXObject(aVersions[iVn]);if(oDoc){return aVersions[iVn];}}catch(oExpn){};}
return null;};Zapatec.Transport.XMLDOM=Zapatec.Transport.pickActiveXVersion(['Msxml2.DOMDocument.4.0','Msxml2.DOMDocument.3.0','MSXML2.DOMDocument','MSXML.DOMDocument','Microsoft.XMLDOM']);Zapatec.Transport.XMLHTTP=Zapatec.Transport.pickActiveXVersion(['Msxml2.XMLHTTP.4.0','MSXML2.XMLHTTP.3.0','MSXML2.XMLHTTP','Microsoft.XMLHTTP']);Zapatec.Transport.pickActiveXVersion=null;}
Zapatec.Transport.createXmlHttpRequest=function(){if(typeof ActiveXObject!='undefined'){try{return new ActiveXObject(Zapatec.Transport.XMLHTTP);}catch(oExpn){};}
if(typeof XMLHttpRequest!='undefined'){return new XMLHttpRequest();}
return null;};Zapatec.Transport.isBusy=function(oArg){var oContr=oArg.busyContainer;if(typeof oContr=='string'){oContr=document.getElementById(oContr);}
if(!oContr){return;}
var sImage=oArg.busyImage;if(typeof sImage!='string'){sImage='';}
sImage=sImage.split('/').pop();if(!sImage.length){sImage='zpbusy.gif';}
var oFC=oContr.firstChild;if(oFC){oFC=oFC.firstChild;if(oFC){oFC=oFC.firstChild;if(oFC&&oFC.tagName&&oFC.tagName.toLowerCase()=='img'){var sSrc=oFC.getAttribute('src');if(typeof sSrc=='string'&&sSrc.length){sSrc=sSrc.split('/').pop();if(sSrc==sImage){return true;}}}}}
return false;};Zapatec.Transport.showBusy=function(oArg){if(Zapatec.Transport.isBusy(oArg)){return;}
var oContr=oArg.busyContainer;if(typeof oContr=='string'){oContr=document.getElementById(oContr);}
if(!oContr){return;}
var sImage=oArg.busyImage;var sImageWidth=oArg.busyImageWidth;var sImageHeight=oArg.busyImageHeight;if(typeof sImage!='string'||!sImage.length){sImage='zpbusy.gif';}else{if(typeof sImageWidth=='number'||(typeof sImageWidth=='string'&&/\d$/.test(sImageWidth))){sImageWidth+='px';}
if(typeof sImageHeight=='number'||(typeof sImageHeight=='string'&&/\d$/.test(sImageHeight))){sImageHeight+='px';}}
if(!sImageWidth){sImageWidth='65px';}
if(!sImageHeight){sImageHeight='35px';}
var sPath='';if(sImage.indexOf('/')<0){if(Zapatec.zapatecPath){sPath=Zapatec.zapatecPath;}else{sPath=Zapatec.Transport.getPath('transport.js');}}
var aImg=[];aImg.push('<img src="');aImg.push(sPath);aImg.push(sImage);aImg.push('"');if(sImageWidth||sImageHeight){aImg.push(' style="');if(sImageWidth){aImg.push('width:');aImg.push(sImageWidth);aImg.push(';');}
if(sImageHeight){aImg.push('height:');aImg.push(sImageHeight);}
aImg.push('"');}
aImg.push(' />');var iContainerWidth=oContr.offsetWidth;var iContainerHeight=oContr.offsetHeight;var oBusyContr=Zapatec.Utils.createElement('div');oBusyContr.style.position='relative';oBusyContr.style.zIndex=2147483583;var oBusy=Zapatec.Utils.createElement('div',oBusyContr);oBusy.style.position='absolute';oBusy.innerHTML=aImg.join('');if(oContr.firstChild){oContr.insertBefore(oBusyContr,oContr.firstChild);}else{oContr.appendChild(oBusyContr);}
var iBusyWidth=oBusy.offsetWidth;var iBusyHeight=oBusy.offsetHeight;if(iContainerWidth>iBusyWidth){oBusy.style.left=oContr.scrollLeft+
(iContainerWidth-iBusyWidth)/2+'px';}
if(iContainerHeight>iBusyHeight){oBusy.style.top=oContr.scrollTop+
(iContainerHeight-iBusyHeight)/2+'px';}};Zapatec.Transport.removeBusy=function(oArg){var oContr=oArg.busyContainer;if(typeof oContr=='string'){oContr=document.getElementById(oContr);}
if(!oContr){return;}
if(Zapatec.Transport.isBusy(oArg)){oContr.removeChild(oContr.firstChild);}};Zapatec.Transport.fetch=function(oArg){if(oArg==null||typeof oArg!='object'){return null;}
if(!oArg.url){return null;}
if(!oArg.method){oArg.method='GET';}
if(typeof oArg.async=='undefined'){oArg.async=true;}
if(!oArg.contentType&&oArg.method.toUpperCase()=='POST'){oArg.contentType='application/x-www-form-urlencoded';}
if(!oArg.content){oArg.content=null;}
if(!oArg.onLoad){oArg.onLoad=null;}
if(!oArg.onError){oArg.onError=null;}
var oRequest=Zapatec.Transport.createXmlHttpRequest();if(oRequest==null){return null;}
Zapatec.Transport.showBusy(oArg);var bErrorDisplayed=false;var funcOnReady=function(){Zapatec.Transport.removeBusy(oArg);try{if(oRequest.status==200||oRequest.status==304||(location.protocol=='file:'&&!oRequest.status)){if(typeof oArg.onLoad=='function'){oArg.onLoad(oRequest);}}else if(!bErrorDisplayed){bErrorDisplayed=true;Zapatec.Transport.displayError(oRequest.status,"Error: Can't fetch "+oArg.url+'.\n'+
(oRequest.statusText||''),oArg.onError);}}catch(oExpn){if(!bErrorDisplayed){bErrorDisplayed=true;if(oExpn.name&&oExpn.name=='NS_ERROR_NOT_AVAILABLE'){Zapatec.Transport.displayError(0,"Error: Can't fetch "+oArg.url+'.\nFile not found.',oArg.onError);}else{Zapatec.Transport.displayError(0,"Error: Can't fetch "+oArg.url+'.\n'+
(oExpn.message||''),oArg.onError);}}};};try{if(typeof oArg.username!='undefined'&&typeof oArg.password!='undefined'){oRequest.open(oArg.method,oArg.url,oArg.async,oArg.username,oArg.password);}else{oRequest.open(oArg.method,oArg.url,oArg.async);}
if(oArg.async){oRequest.onreadystatechange=function(){if(oRequest.readyState==4){funcOnReady();oRequest.onreadystatechange={};}};}
if(oArg.contentType){oRequest.setRequestHeader('Content-Type',oArg.contentType);}
oRequest.send(oArg.content);if(!oArg.async){funcOnReady();return oRequest;}}catch(oExpn){Zapatec.Transport.removeBusy(oArg);if(!bErrorDisplayed){bErrorDisplayed=true;if(oExpn.name&&oExpn.name=='NS_ERROR_FILE_NOT_FOUND'){Zapatec.Transport.displayError(0,"Error: Can't fetch "+oArg.url+'.\nFile not found.',oArg.onError);}else{Zapatec.Transport.displayError(0,"Error: Can't fetch "+oArg.url+'.\n'+
(oExpn.message||''),oArg.onError);}}};return null;};Zapatec.Transport.parseHtml=function(sHtml){sHtml+='';sHtml=sHtml.replace(/^\s+/g,'');var oTmpContr;if(document.createElementNS){oTmpContr=document.createElementNS('http://www.w3.org/1999/xhtml','div');}else{oTmpContr=document.createElement('div');}
oTmpContr.innerHTML=sHtml;return oTmpContr;};Zapatec.Transport.evalGlobalScope=function(sScript){if(typeof sScript!='string'||!sScript.match(/\S/)){return;}
if(window.execScript){window.execScript(sScript,'javascript');}else if(window.eval){window.eval(sScript);}};Zapatec.Transport.setInnerHtml=function(oArg){if(!oArg||typeof oArg.html!='string'){return;}
var sHtml=oArg.html;var oContr=null;if(typeof oArg.container=='string'){oContr=document.getElementById(oArg.container);}else if(typeof oArg.container=='object'){oContr=oArg.container;}
var aScripts=[];if(sHtml.match(/<\s*\/\s*script\s*>/i)){var aTokens=sHtml.split(/<\s*\/\s*script\s*>/i);var aHtml=[];for(var iToken=aTokens.length-1;iToken>=0;iToken--){var sToken=aTokens[iToken];if(sToken.match(/\S/)){var aMatch=sToken.match(/<\s*script([^>]*)>/i);if(aMatch){var aCouple=sToken.split(/<\s*script[^>]*>/i);while(aCouple.length<2){if(sToken.match(/^<\s*script[^>]*>/i)){aCouple.unshift('');}else{aCouple.push('');}}
aHtml.unshift(aCouple[0]);var sAttrs=aMatch[1];var srtScript=aCouple[1];if(sAttrs.match(/\s+src\s*=/i)){srtScript='';}else{srtScript=srtScript.replace(/function\s+([^(]+)/g,'$1=function');}
aScripts.push([sAttrs,srtScript]);}else if(iToken<aTokens.length-1){aTokens[iToken-1]+='</script>'+sToken;}else{aHtml.unshift(sToken);}}else{aHtml.unshift(sToken);}}
sHtml=aHtml.join('');}
if(oContr){if(window.opera){oContr.innerHTML='<form></form>';}
oContr.innerHTML=sHtml;}
for(var iScript=0;iScript<aScripts.length;iScript++){if(aScripts[iScript][1].length){Zapatec.Transport.evalGlobalScope(aScripts[iScript][1]);}
var sAttrs=aScripts[iScript][0];sAttrs=sAttrs.replace(/\s+/g,' ').replace(/^\s/,'').replace(/\s$/,'').replace(/ = /g,'=');if(sAttrs.indexOf('src=')>=0){var oContr=document.body;if(!oContr){oContr=document.getElementsByTagName('head')[0];if(!oContr){oContr=document;}}
var aAttrs=sAttrs.split(' ');var oScript=Zapatec.Utils.createElement('script');for(var iAttr=0;iAttr<aAttrs.length;iAttr++){var aAttr=aAttrs[iAttr].split('=');if(aAttr.length>1){oScript.setAttribute(aAttr[0],aAttr[1].match(/^[\s|"|']*([\s|\S]*[^'|"])[\s|"|']*$/)[1]);}else{oScript.setAttribute(aAttr[0],aAttr[0]);}}
oContr.appendChild(oScript);}}};Zapatec.Transport.fetchXmlDoc=function(oArg){if(oArg==null||typeof oArg!='object'){return null;}
if(!oArg.url){return null;}
if(typeof oArg.async=='undefined'){oArg.async=true;}
if(!oArg.onLoad){oArg.onLoad=null;}
if(!oArg.onError){oArg.onError=null;}
if(!oArg.method&&typeof oArg.username=='undefined'&&typeof oArg.password=='undefined'){if(document.implementation&&document.implementation.createDocument){var oDoc=null;if(!oArg.reliable){oArg.reliable=false;}
var oFetchArg={};for(var sKey in oArg){oFetchArg[sKey]=oArg[sKey];}
if(oArg.async){oFetchArg.onLoad=function(oRequest){oFetchArg.onLoad=null;var parser=new DOMParser();oDoc=parser.parseFromString(oRequest.responseText,"text/xml");Zapatec.Transport.removeBusy(oArg);Zapatec.Transport.onXmlDocLoad(oDoc,oArg.onLoad,oArg.onError);};}else{oFetchArg.onLoad=null;}
var oRequest=Zapatec.Transport.fetch(oFetchArg);if(!oArg.async&&oRequest){var parser=new DOMParser();oDoc=parser.parseFromString(oRequest.responseText,"text/xml");Zapatec.Transport.removeBusy(oArg);Zapatec.Transport.onXmlDocLoad(oDoc,oArg.onLoad,oArg.onError);return oDoc;}
return null;}
if(typeof ActiveXObject!='undefined'){Zapatec.Transport.showBusy(oArg);try{var oDoc=new ActiveXObject(Zapatec.Transport.XMLDOM);oDoc.async=oArg.async;if(oArg.async){oDoc.onreadystatechange=function(){if(oDoc.readyState==4){Zapatec.Transport.removeBusy(oArg);Zapatec.Transport.onXmlDocLoad(oDoc,oArg.onLoad,oArg.onError);oDoc.onreadystatechange={};}};}
oDoc.load(oArg.url);if(!oArg.async){Zapatec.Transport.removeBusy(oArg);Zapatec.Transport.onXmlDocLoad(oDoc,oArg.onLoad,oArg.onError);return oDoc;}
return null;}catch(oExpn){Zapatec.Transport.removeBusy(oArg);};}}
var oFetchArg={};for(var sKey in oArg){oFetchArg[sKey]=oArg[sKey];}
if(oArg.async){oFetchArg.onLoad=function(oRequest){Zapatec.Transport.parseXml({strXml:oRequest.responseText,onLoad:oArg.onLoad,onError:oArg.onError});};}else{oFetchArg.onLoad=null;}
var oRequest=Zapatec.Transport.fetch(oFetchArg);if(!oArg.async&&oRequest){return Zapatec.Transport.parseXml({strXml:oRequest.responseText,onLoad:oArg.onLoad,onError:oArg.onError});}
return null;};Zapatec.Transport.parseXml=function(oArg){if(oArg==null||typeof oArg!='object'){return null;}
if(!oArg.strXml){return null;}
if(!oArg.onLoad){oArg.onLoad=null;}
if(!oArg.onError){oArg.onError=null;}
if(window.DOMParser){try{var oDoc=(new DOMParser()).parseFromString(oArg.strXml,'text/xml');Zapatec.Transport.onXmlDocLoad(oDoc,oArg.onLoad,oArg.onError);return oDoc;}catch(oExpn){Zapatec.Transport.displayError(0,"Error: Can't parse.\n"+'String does not appear to be a valid XML fragment.',oArg.onError);};return null;}
if(typeof ActiveXObject!='undefined'){try{var oDoc=new ActiveXObject(Zapatec.Transport.XMLDOM);oDoc.loadXML(oArg.strXml);Zapatec.Transport.onXmlDocLoad(oDoc,oArg.onLoad,oArg.onError);return oDoc;}catch(oExpn){};}
return null;};Zapatec.Transport.onXmlDocLoad=function(oDoc,onLoad,onError){var sError=null;if(oDoc.parseError){sError=oDoc.parseError.reason;if(oDoc.parseError.srcText){sError+='Location: '+oDoc.parseError.url+'\nLine number '+oDoc.parseError.line+', column '+
oDoc.parseError.linepos+':\n'+
oDoc.parseError.srcText+'\n';}}else if(oDoc.documentElement&&oDoc.documentElement.tagName=='parsererror'){sError=oDoc.documentElement.firstChild.data+'\n'+
oDoc.documentElement.firstChild.nextSibling.firstChild.data;}else if(!oDoc.documentElement){sError='String does not appear to be a valid XML fragment.';}
if(sError){Zapatec.Transport.displayError(0,"Error: Can't parse.\n"+sError,onError);}else{if(typeof onLoad=='function'){onLoad(oDoc);}}};Zapatec.Transport.serializeXmlDoc=function(oDoc){if(window.XMLSerializer){return(new XMLSerializer).serializeToString(oDoc);}
if(oDoc.xml){return oDoc.xml;}};Zapatec.Transport.fetchJsonObj=function(oArg){if(oArg==null||typeof oArg!='object'){return null;}
if(!oArg.url){return null;}
if(typeof oArg.async=='undefined'){oArg.async=true;}
if(!oArg.reliable){oArg.reliable=false;}
var oFetchArg={};for(var sKey in oArg){oFetchArg[sKey]=oArg[sKey];}
if(oArg.async){oFetchArg.onLoad=function(oRequest){Zapatec.Transport.parseJson({strJson:oRequest.responseText,reliable:oArg.reliable,onLoad:oArg.onLoad,onError:oArg.onError});};}else{oFetchArg.onLoad=null;}
var oRequest=Zapatec.Transport.fetch(oFetchArg);if(!oArg.async&&oRequest){return Zapatec.Transport.parseJson({strJson:oRequest.responseText,reliable:oArg.reliable,onLoad:oArg.onLoad,onError:oArg.onError});}
return null;};Zapatec.Transport.parseJson=function(oArg){if(oArg==null||typeof oArg!='object'){return null;}
if(!oArg.reliable){oArg.reliable=false;}
if(!oArg.onLoad){oArg.onLoad=null;}
if(!oArg.onError){oArg.onError=null;}
var oJson=null;try{if(oArg.reliable){if(oArg.strJson){oJson=eval('('+oArg.strJson+')');}}else{oJson=Zapatec.Transport.parseJsonStr(oArg.strJson);}}catch(oExpn){var sError="Error: Can't parse.\nString doesn't appear to be a valid JSON fragment: ";sError+=oExpn.message;if(typeof oExpn.text!='undefined'&&oExpn.text.length){sError+='\n'+oExpn.text;}
sError+='\n'+oArg.strJson;Zapatec.Transport.displayError(0,sError,oArg.onError);return null;};if(typeof oArg.onLoad=='function'){oArg.onLoad(oJson);}
return oJson;};Zapatec.Transport.parseJsonStr=function(text){var p=/^\s*(([,:{}\[\]])|"(\\.|[^\x00-\x1f"\\])*"|-?\d+(\.\d*)?([eE][+-]?\d+)?|true|false|null)\s*/,token,operator;function error(m,t){throw{name:'JSONError',message:m,text:t||operator||token};}
function next(b){if(b&&b!=operator){error("Expected '"+b+"'");}
if(text){var t=p.exec(text);if(t){if(t[2]){token=null;operator=t[2];}else{operator=null;try{token=eval(t[1]);}catch(e){error("Bad token",t[1]);}}
text=text.substring(t[0].length);}else{error("Unrecognized token",text);}}else{token=operator=null;}}
function val(){var k,o;switch(operator){case'{':next('{');o={};if(operator!='}'){for(;;){if(operator||typeof token!='string'){error("Missing key");}
k=token;next();next(':');o[k]=val();if(operator!=','){break;}
next(',');}}
next('}');return o;case'[':next('[');o=[];if(operator!=']'){for(;;){o.push(val());if(operator!=','){break;}
next(',');}}
next(']');return o;default:if(operator!==null){error("Missing value");}
k=token;next();return k;}}
next();return val();};Zapatec.Transport.serializeJsonObj=function(v){var a=[];function e(s){a[a.length]=s;}
function g(x){var c,i,l,v;switch(typeof x){case'object':if(x){if(x instanceof Array){e('[');l=a.length;for(i=0;i<x.length;i+=1){v=x[i];if(typeof v!='undefined'&&typeof v!='function'){if(l<a.length){e(',');}
g(v);}}
e(']');return;}else if(typeof x.toString!='undefined'){e('{');l=a.length;for(i in x){v=x[i];if(x.hasOwnProperty(i)&&typeof v!='undefined'&&typeof v!='function'){if(l<a.length){e(',');}
g(i);e(':');g(v);}}
return e('}');}}
e('null');return;case'number':e(isFinite(x)?+x:'null');return;case'string':l=x.length;e('"');for(i=0;i<l;i+=1){c=x.charAt(i);if(c>=' '){if(c=='\\'||c=='"'){e('\\');}
e(c);}else{switch(c){case'\b':e('\\b');break;case'\f':e('\\f');break;case'\n':e('\\n');break;case'\r':e('\\r');break;case'\t':e('\\t');break;default:c=c.charCodeAt();e('\\u00'+Math.floor(c/16).toString(16)+
(c%16).toString(16));}}}
e('"');return;case'boolean':e(String(x));return;default:e('null');return;}}
g(v);return a.join('');};Zapatec.Transport.displayError=function(iErrCode,sError,onError){if(typeof onError=='function'){onError({errorCode:iErrCode,errorDescription:sError});}else{alert(sError);}};Zapatec.Transport.translateUrl=function(oArg){if(!oArg||!oArg.url){return null;}
var aFullUrl=oArg.url.split('?',2);var sUrl=aFullUrl[0];if(sUrl.charAt(0)=='/'||sUrl.indexOf(':')>=0){return oArg.url;}
var sRelativeTo;if(typeof oArg.relativeTo!='string'){sRelativeTo=document.location.toString().split('?',2)[0];}else{sRelativeTo=oArg.relativeTo.split('?',2)[0];if(sRelativeTo.indexOf('/')<0){sRelativeTo=document.location.toString().split('?',2)[0];}else if(sRelativeTo.charAt(0)!='/'&&sRelativeTo.indexOf(':')<0){sRelativeTo=Zapatec.Transport.translateUrl({url:sRelativeTo});}}
var aUrl=sUrl.split('/');var aRelativeTo=sRelativeTo.split('/');aRelativeTo.pop();for(var iToken=0;iToken<aUrl.length;iToken++){var sToken=aUrl[iToken];if(sToken=='..'){aRelativeTo.pop();}else if(sToken!='.'){aRelativeTo.push(sToken);}}
aFullUrl[0]=aRelativeTo.join('/');return aFullUrl.join('?');};Zapatec.Transport.loading={};Zapatec.Transport.setupEvents=function(oArg){if(!oArg){return{};}
if(oArg.force||!Zapatec.EventDriven||!oArg.url){return{onLoad:oArg.onLoad,onError:oArg.onError};}
var sUrl=oArg.url;if(typeof oArg.onLoad=='function'){Zapatec.EventDriven.addEventListener('zpTransportOnLoad'+sUrl,oArg.onLoad);}
if(typeof oArg.onError=='function'){Zapatec.EventDriven.addEventListener('zpTransportOnError'+sUrl,oArg.onError);}
if(Zapatec.Transport.loading[sUrl]){return{loading:true};}else{Zapatec.Transport.loading[sUrl]=true;return{onLoad:new Function("Zapatec.EventDriven.fireEvent('zpTransportOnLoad"+
sUrl+"');Zapatec.EventDriven.removeEvent('zpTransportOnLoad"+
sUrl+"');Zapatec.EventDriven.removeEvent('zpTransportOnError"+
sUrl+"');Zapatec.Transport.loading['"+sUrl+"'] = false;"),onError:new Function('oError',"Zapatec.EventDriven.fireEvent('zpTransportOnError"+
sUrl+"',oError);Zapatec.EventDriven.removeEvent('zpTransportOnLoad"+
sUrl+"');Zapatec.EventDriven.removeEvent('zpTransportOnError"+
sUrl+"');Zapatec.Transport.loading['"+sUrl+"'] = false;")};}};Zapatec.Transport.loadedJS={};Zapatec.Transport.isLoadedJS=function(sUrl,sAbsUrl){if(typeof sAbsUrl=='undefined'){sAbsUrl=Zapatec.Transport.translateUrl({url:sUrl});}
if(Zapatec.Transport.loadedJS[sAbsUrl]){return true;}
var aScripts=document.getElementsByTagName('script');for(var iScript=0;iScript<aScripts.length;iScript++){var sSrc=aScripts[iScript].getAttribute('src')||'';if(sSrc==sUrl){Zapatec.Transport.loadedJS[sAbsUrl]=true;return true;}}
return false;};Zapatec.Transport.getPath=function(sScriptFileName){var aScripts=document.getElementsByTagName('script');for(var iScript=aScripts.length-1;iScript>=0;iScript--){var sSrc=aScripts[iScript].getAttribute('src')||'';var aTokens=sSrc.split('/');var sLastToken=aTokens.pop();if(sLastToken==sScriptFileName){return aTokens.length?aTokens.join('/')+'/':'';}}
for(var sSrc in Zapatec.Transport.loadedJS){var aTokens=sSrc.split('/');var sLastToken=aTokens.pop();if(sLastToken==sScriptFileName){return aTokens.length?aTokens.join('/')+'/':'';}}
return'';};Zapatec.Transport.include=function(sSrc,sId,bForce){if(Zapatec.doNotInclude){return;}
var sAbsUrl=Zapatec.Transport.translateUrl({url:sSrc});if(!bForce&&Zapatec.Transport.isLoadedJS(sSrc,sAbsUrl)){return;}
document.write('<script type="text/javascript" src="'+sSrc+
(typeof sId=='string'?'" id="'+sId:'')+'"></script>');Zapatec.Transport.loadedJS[sAbsUrl]=true;};Zapatec.include=Zapatec.Transport.include;Zapatec.Transport.includeJS=function(sSrc,sId){setTimeout(function(){var oContr=document.body;if(!oContr){oContr=document.getElementsByTagName('head')[0];if(!oContr){oContr=document;}}
var oScript=document.createElement('script');oScript.type='text/javascript';oScript.src=sSrc;if(typeof sId=='string'){oScript.id=sId;}
oContr.appendChild(oScript);},0);};Zapatec.Transport.loadJS=function(oArg){if(!(oArg instanceof Object)){return;}
if(typeof oArg.async=='undefined'){oArg.async=true;}
var sUrl=null;if(oArg.url){sUrl=oArg.url;}else if(oArg.module){var sPath='';if(typeof oArg.path!='undefined'){sPath=oArg.path;}else if(typeof Zapatec.zapatecPath!='undefined'){sPath=Zapatec.zapatecPath;}
sUrl=sPath+oArg.module+'.js';}else{return;}
var sAbsUrl=Zapatec.Transport.translateUrl({url:sUrl});if(!oArg.onLoad){oArg.onLoad=null;}
if(!oArg.onError){oArg.onError=null;}
if(Zapatec.doNotInclude||(!oArg.force&&Zapatec.Transport.isLoadedJS(sUrl,sAbsUrl))){if(typeof oArg.onLoad=='function'){oArg.onLoad();}
return;}
var oHandlers=Zapatec.Transport.setupEvents({url:sAbsUrl,force:oArg.force,onLoad:oArg.onLoad,onError:oArg.onError});if(oHandlers.loading){return;}
Zapatec.Transport.fetch({url:sUrl,async:oArg.async,onLoad:function(oRequest){if(oArg.force||!Zapatec.Transport.loadedJS[sAbsUrl]){var aTokens=sUrl.split('/');var sLastToken=aTokens.pop();Zapatec.lastLoadedModule=aTokens.join('/')+'/';Zapatec.Transport.evalGlobalScope(oRequest.responseText);Zapatec.lastLoadedModule=null;Zapatec.Transport.loadedJS[sAbsUrl]=true;}
if(typeof oHandlers.onLoad=='function'){oHandlers.onLoad();}},onError:oHandlers.onError});};Zapatec.Transport.includeCSS=function(sHref){var oContr=document.getElementsByTagName('head')[0];if(!oContr){return;}
var oLink=document.createElement('link');oLink.setAttribute('rel','stylesheet');oLink.setAttribute('type','text/css');oLink.setAttribute('href',sHref);oContr.appendChild(oLink);};Zapatec.Transport.loadedCss={};Zapatec.Transport.loadCss=function(oArg){if(Zapatec.StyleSheet){Zapatec.Transport.loadCssWithStyleSheet(oArg);}else{Zapatec.Transport.loadJS({module:'stylesheet',async:oArg.async,onLoad:function(){Zapatec.Transport.loadCssWithStyleSheet(oArg);}});}};Zapatec.Transport.loadCssWithStyleSheet=function(oArg){if(!(oArg instanceof Object)){return;}
if(!oArg.url){return;}
if(typeof oArg.async=='undefined'){oArg.async=true;}
var sAbsUrl=Zapatec.Transport.translateUrl({url:oArg.url});if(!oArg.force){if(Zapatec.Transport.loadedCss[sAbsUrl]){if(typeof oArg.onLoad=='function'){oArg.onLoad();}
return;}
var aLinks=document.getElementsByTagName('link');for(var iLnk=0;iLnk<aLinks.length;iLnk++){var sHref=aLinks[iLnk].getAttribute('href')||'';sHref=Zapatec.Transport.translateUrl({url:sHref});if(sHref==sAbsUrl){Zapatec.Transport.loadedCss[sAbsUrl]=true;if(typeof oArg.onLoad=='function'){oArg.onLoad();}
return;}}}
var oHandlers=Zapatec.Transport.setupEvents({url:sAbsUrl,force:oArg.force,onLoad:oArg.onLoad,onError:oArg.onError});if(oHandlers.loading){return;}
Zapatec.Transport.fetch({url:oArg.url,async:oArg.async,onLoad:function(oRequest){var sCss=oRequest.responseText;var aResultCss=[];var aImgUrls=[];var aCssUrls=[];var iPos=0;var iNextPos=sCss.indexOf('url(',iPos);while(iNextPos>=0){iNextPos+=4;var sToken=sCss.substring(iPos,iNextPos);var bIsImport=/@import\s+url\($/.test(sToken);aResultCss.push(sToken);iPos=iNextPos;iNextPos=sCss.indexOf(')',iPos);if(iNextPos>=0){var sImgUrl=sCss.substring(iPos,iNextPos);sImgUrl=sImgUrl.replace(/['"]/g,'');sImgUrl=Zapatec.Transport.translateUrl({url:sImgUrl,relativeTo:oArg.url});sImgUrl=Zapatec.Transport.translateUrl({url:sImgUrl});aResultCss.push(sImgUrl);if(bIsImport){aCssUrls.push(sImgUrl);}else{aImgUrls.push(sImgUrl);}
iPos=iNextPos;iNextPos=sCss.indexOf('url(',iPos);}}
aResultCss.push(sCss.substr(iPos));sCss=aResultCss.join('');Zapatec.Transport.loadCssList({urls:aCssUrls,async:oArg.async,onLoad:function(){(new Zapatec.StyleSheet()).addParse(sCss);if(typeof oHandlers.onLoad=='function'){oHandlers.onLoad();}}});Zapatec.Transport.loadedCss[sAbsUrl]=true;Zapatec.Transport.preloadImages({urls:aImgUrls,timeout:60000});},onError:oHandlers.onError});};Zapatec.Transport.loadCssList=function(oArg){if(!(oArg instanceof Object)){return;}
if(typeof oArg.async=='undefined'){oArg.async=true;}
if(!oArg.onLoad){oArg.onLoad=null;}
if(!oArg.onError){oArg.onError=null;}
if(!oArg.urls||!oArg.urls.length){if(typeof oArg.onLoad=='function'){oArg.onLoad();}
return;}
var sUrl=oArg.urls.shift();var funcOnLoad=function(){Zapatec.Transport.loadCssList({urls:oArg.urls,async:oArg.async,force:oArg.force,onLoad:oArg.onLoad,onError:oArg.onError});};Zapatec.Transport.loadCss({url:sUrl,async:oArg.async,force:oArg.force,onLoad:funcOnLoad,onError:function(oError){Zapatec.Transport.displayError(oError.errorCode,oError.errorDescription,oArg.onError);funcOnLoad();}});};Zapatec.Transport.imagePreloads=[];Zapatec.Transport.preloadImages=function(oArg){if(Zapatec.PreloadImages){Zapatec.Transport.imagePreloads.push(new Zapatec.PreloadImages(oArg));}else{Zapatec.Transport.loadJS({module:'preloadimages',onLoad:function(){Zapatec.Transport.imagePreloads.push(new Zapatec.PreloadImages(oArg));}});}};if(typeof Zapatec=='undefined'){Zapatec=function(){};}
Zapatec.StyleSheet=function(bUseLast){if(bUseLast){if(document.createStyleSheet){if(document.styleSheets.length){this.styleSheet=document.styleSheets[document.styleSheets.length-1];}}else{var aStyleSheets=document.getElementsByTagName('style');if(aStyleSheets.length){this.styleSheet=aStyleSheets[aStyleSheets.length-1];}}}
if(!this.styleSheet){if(document.createStyleSheet){try{this.styleSheet=document.createStyleSheet();}catch(oException){this.styleSheet=document.styleSheets[document.styleSheets.length-1];};}else{this.styleSheet=document.createElement('style');this.styleSheet.type='text/css';var oHead=document.getElementsByTagName('head')[0];if(!oHead){oHead=document.documentElement;}
if(oHead){oHead.appendChild(this.styleSheet);}}}};Zapatec.StyleSheet.prototype.addRule=function(strSelector,strDeclarations){if(!this.styleSheet){return;}
if(document.createStyleSheet){this.styleSheet.cssText+=strSelector+' { '+strDeclarations+' }';}else{this.styleSheet.appendChild(document.createTextNode(strSelector+' { '+strDeclarations+' }'));}};Zapatec.StyleSheet.prototype.removeRules=function(){if(!this.styleSheet){return;}
if(document.createStyleSheet){var iRules=this.styleSheet.rules.length;for(var iRule=0;iRule<iRules;iRule++){this.styleSheet.removeRule();}}else{while(this.styleSheet.firstChild){this.styleSheet.removeChild(this.styleSheet.firstChild);}}};Zapatec.StyleSheet.prototype.addParse=function(strStyleSheet){var arrClean=[];var arrTokens=strStyleSheet.split('/*');for(var iTok=0;iTok<arrTokens.length;iTok++){var arrTails=arrTokens[iTok].split('*/');arrClean.push(arrTails[arrTails.length-1]);}
strStyleSheet=arrClean.join('');strStyleSheet=strStyleSheet.replace(/@[^{]*;/g,'');var arrStyles=strStyleSheet.split('}');for(var iStl=0;iStl<arrStyles.length;iStl++){var arrRules=arrStyles[iStl].split('{');if(arrRules[0]&&arrRules[1]){var arrSelectors=arrRules[0].split(',');for(var iSel=0;iSel<arrSelectors.length;iSel++){this.addRule(arrSelectors[iSel],arrRules[1]);}}}};Zapatec.ImagePreloader=function(objArgs){this.job=null;this.image=null;if(arguments.length>0)this.init(objArgs);};Zapatec.ImagePreloader.prototype.init=function(objArgs){if(!objArgs||!objArgs.job){return;}
this.job=objArgs.job;this.image=new Image();this.job.images.push(this.image);var objPreloader=this;this.image.onload=function(){objPreloader.job.loadedUrls.push(objArgs.url);setTimeout(function(){objPreloader.onLoad();},0);};this.image.onerror=function(){objPreloader.job.invalidUrls.push(objArgs.url);objPreloader.onLoad();};this.image.onabort=function(){objPreloader.job.abortedUrls.push(objArgs.url);objPreloader.onLoad();};this.image.src=objArgs.url;if(typeof objArgs.timeout=='number'){setTimeout(function(){if(objPreloader.job){if(objPreloader.image.complete){objPreloader.job.loadedUrls.push(objArgs.url);}else{objPreloader.job.abortedUrls.push(objArgs.url);}
objPreloader.onLoad();}},objArgs.timeout);}};Zapatec.ImagePreloader.prototype.onLoad=function(){if(!this.job){return;}
this.image.onload=null;this.image.onerror=null;this.image.onabort=null;var objJob=this.job;this.job=null;objJob.leftToLoad--;if(objJob.leftToLoad==0&&typeof objJob.onLoad=='function'){var funcOnLoad=objJob.onLoad;objJob.onLoad=null;funcOnLoad(objJob);}};Zapatec.PreloadImages=function(objArgs){this.images=[];this.leftToLoad=0;this.loadedUrls=[];this.invalidUrls=[];this.abortedUrls=[];this.onLoad=null;if(arguments.length>0)this.init(objArgs);};Zapatec.PreloadImages.prototype.init=function(objArgs){if(!objArgs){return;}
if(!objArgs.urls||!objArgs.urls.length){if(typeof objArgs.onLoad=='function'){objArgs.onLoad(this);}
return;}
this.images=[];this.leftToLoad=objArgs.urls.length;this.loadedUrls=[];this.invalidUrls=[];this.abortedUrls=[];this.onLoad=objArgs.onLoad;for(var iUrl=0;iUrl<objArgs.urls.length;iUrl++){new Zapatec.ImagePreloader({job:this,url:objArgs.urls[iUrl],timeout:objArgs.timeout});}};if(typeof Zapatec=='undefined'){Zapatec=function(){};}
Zapatec.EventDriven=function(){};Zapatec.EventDriven.prototype.init=function(){this.events={};};Zapatec.EventDriven.prototype.addEventListener=function(sEvent,fListener){if(typeof fListener!="function"){return false;}
if(!this.events[sEvent]){this.events[sEvent]={listeners:[]};}else{this.removeEventListener(sEvent,fListener);}
this.events[sEvent].listeners.push(fListener);};Zapatec.EventDriven.prototype.unshiftEventListener=function(sEvent,fListener){if(typeof fListener!="function"){return false;}
if(!this.events[sEvent]){this.events[sEvent]={listeners:[]};}else{this.removeEventListener(sEvent,fListener);}
this.events[sEvent].listeners.unshift(fListener);};Zapatec.EventDriven.prototype.removeEventListener=function(sEvent,fListener){if(!this.events[sEvent]){return 0;}
var aListeners=this.events[sEvent].listeners;var iRemoved=0;for(var iListener=aListeners.length-1;iListener>=0;iListener--){if(aListeners[iListener]==fListener){aListeners.splice(iListener,1);iRemoved++;}}
return iRemoved;};Zapatec.EventDriven.prototype.getEventListeners=function(sEvent){if(!this.events[sEvent]){return[];}
return this.events[sEvent].listeners;};Zapatec.EventDriven.prototype.isEventListener=function(sEvent,fListener){if(!this.events[sEvent]){return false;}
var aListeners=this.events[sEvent].listeners;for(var iListener=aListeners.length-1;iListener>=0;iListener--){if(aListeners[iListener]==fListener){return true;}}
return false;};Zapatec.EventDriven.prototype.isEvent=function(sEvent){if(this.events[sEvent]){return true;}
return false;};Zapatec.EventDriven.prototype.removeEvent=function(sEvent){if(this.events[sEvent]){var undef;this.events[sEvent]=undef;}};Zapatec.EventDriven.prototype.fireEvent=function(sEvent){if(!this.events[sEvent]){return;}
var aListeners=this.events[sEvent].listeners.slice();for(var iListener=0;iListener<aListeners.length;iListener++){var aArgs=[].slice.call(arguments,1);aListeners[iListener].apply(this,aArgs);}};Zapatec.EventDriven.events={};Zapatec.EventDriven.addEventListener=function(sEvent,fListener){if(typeof fListener!="function"){return false;}
if(!Zapatec.EventDriven.events[sEvent]){Zapatec.EventDriven.events[sEvent]={listeners:[]};}else{Zapatec.EventDriven.removeEventListener(sEvent,fListener);}
Zapatec.EventDriven.events[sEvent].listeners.push(fListener);};Zapatec.EventDriven.unshiftEventListener=function(sEvent,fListener){if(typeof fListener!="function"){return false;}
if(!Zapatec.EventDriven.events[sEvent]){Zapatec.EventDriven.events[sEvent]={listeners:[]};}else{Zapatec.EventDriven.removeEventListener(sEvent,fListener);}
Zapatec.EventDriven.events[sEvent].listeners.unshift(fListener);};Zapatec.EventDriven.removeEventListener=function(sEvent,fListener){if(!Zapatec.EventDriven.events[sEvent]){return 0;}
var aListeners=Zapatec.EventDriven.events[sEvent].listeners;var iRemoved=0;for(var iListener=aListeners.length-1;iListener>=0;iListener--){if(aListeners[iListener]==fListener){aListeners.splice(iListener,1);iRemoved++;}}
return iRemoved;};Zapatec.EventDriven.getEventListeners=function(sEvent){if(!Zapatec.EventDriven.events[sEvent]){return[];}
return Zapatec.EventDriven.events[sEvent].listeners;};Zapatec.EventDriven.isEventListener=function(sEvent,fListener){if(!Zapatec.EventDriven.events[sEvent]){return false;}
var aListeners=Zapatec.EventDriven.events[sEvent].listeners;for(var iListener=aListeners.length-1;iListener>=0;iListener--){if(aListeners[iListener]==fListener){return true;}}
return false;};Zapatec.EventDriven.isEvent=function(sEvent){if(Zapatec.EventDriven.events[sEvent]){return true;}
return false;};Zapatec.EventDriven.removeEvent=function(sEvent){if(Zapatec.EventDriven.events[sEvent]){var undef;Zapatec.EventDriven.events[sEvent]=undef;}};Zapatec.EventDriven.fireEvent=function(sEvent){if(!Zapatec.EventDriven.events[sEvent]){return;}
var aListeners=Zapatec.EventDriven.events[sEvent].listeners.slice();for(var iListener=0;iListener<aListeners.length;iListener++){var aArgs=[].slice.call(arguments,1);aListeners[iListener].apply(aListeners[iListener],aArgs);}};if(typeof Zapatec=='undefined'){Zapatec=function(){};}
Zapatec.Widget=function(oArg){this.config={};Zapatec.Widget.SUPERconstructor.call(this);this.init(oArg);};Zapatec.inherit(Zapatec.Widget,Zapatec.EventDriven);Zapatec.Widget.path=Zapatec.getPath('Zapatec.Widget');Zapatec.Widget.prototype.init=function(oArg){Zapatec.Widget.SUPERclass.init.call(this);if(typeof this.id=='undefined'){var iId=0;while(Zapatec.Widget.all[iId]){iId++;}
this.id=iId;Zapatec.Widget.all[iId]=this;}
this.configure(oArg);this.addUserEventListeners();this.addStandardEventListeners();this.loadTheme();};Zapatec.Widget.prototype.reconfigure=function(oArg){this.configure(oArg);this.loadTheme();};Zapatec.Widget.prototype.configure=function(oArg){this.defineConfigOption('theme','default');if(typeof this.constructor.path!='undefined'){this.defineConfigOption('themePath',this.constructor.path+'../themes/');}else{this.defineConfigOption('themePath','../themes/');}
this.defineConfigOption('asyncTheme',false);this.defineConfigOption('source');this.defineConfigOption('sourceType');this.defineConfigOption('callbackSource');this.defineConfigOption('asyncSource',true);this.defineConfigOption('reliableSource',true);this.defineConfigOption('eventListeners',{});if(oArg){for(var sOption in oArg){if(typeof this.config[sOption]!='undefined'){this.config[sOption]=oArg[sOption];}else{Zapatec.Log({description:"Unknown config option: "+sOption});}}}};Zapatec.Widget.prototype.getConfiguration=function(){return this.config;};Zapatec.Widget.all=[];Zapatec.Widget.getWidgetById=function(iId){return Zapatec.Widget.all[iId];};Zapatec.Widget.prototype.addCircularRef=function(oElement,sProperty){if(!this.widgetCircularRefs){this.widgetCircularRefs=[];}
this.widgetCircularRefs.push([oElement,sProperty]);};Zapatec.Widget.prototype.createProperty=function(oElement,sProperty,val){oElement[sProperty]=val;this.addCircularRef(oElement,sProperty);};Zapatec.Widget.prototype.removeCircularRefs=function(){if(!this.widgetCircularRefs){return;}
for(var iRef=this.widgetCircularRefs.length-1;iRef>=0;iRef--){var oRef=this.widgetCircularRefs[iRef];oRef[0][oRef[1]]=null;oRef[0]=null;}};Zapatec.Widget.prototype.discard=function(){Zapatec.Widget.all[this.id]=null;this.removeCircularRefs();};Zapatec.Widget.removeCircularRefs=function(){for(var iWidget=Zapatec.Widget.all.length-1;iWidget>=0;iWidget--){var oWidget=Zapatec.Widget.all[iWidget];if(oWidget){oWidget.removeCircularRefs();}}};Zapatec.Utils.addEvent(window,'unload',Zapatec.Widget.removeCircularRefs);Zapatec.Widget.prototype.defineConfigOption=function(sOption,val){if(typeof this.config[sOption]=='undefined'){if(typeof val=='undefined'){this.config[sOption]=null;}else{this.config[sOption]=val;}}};Zapatec.Widget.prototype.addUserEventListeners=function(){for(var sEvent in this.config.eventListeners){if(this.config.eventListeners.hasOwnProperty(sEvent)){this.addEventListener(sEvent,this.config.eventListeners[sEvent]);}}};Zapatec.Widget.prototype.addStandardEventListeners=function(){this.addEventListener('loadThemeError',Zapatec.Widget.loadThemeError);};Zapatec.Widget.loadThemeError=function(oError){var sDescription="Can't load theme.";if(oError&&oError.errorDescription){sDescription+=' '+oError.errorDescription;}
Zapatec.Log({description:sDescription});};Zapatec.Widget.prototype.loadTheme=function(){if(typeof this.config.theme=='string'&&this.config.theme.length){var iPos=this.config.theme.lastIndexOf('/');if(iPos>=0){iPos++;this.config.themePath=this.config.theme.substring(0,iPos);this.config.theme=this.config.theme.substring(iPos);}
iPos=this.config.theme.lastIndexOf('.');if(iPos>=0){this.config.theme=this.config.theme.substring(0,iPos);}
this.config.theme=this.config.theme.toLowerCase();}else{this.config.theme='';}
if(this.config.theme){this.fireEvent('loadThemeStart');this.themeLoaded=false;var oWidget=this;var sUrl=this.config.themePath+this.config.theme+'.css';Zapatec.Transport.loadCss({url:sUrl,async:this.config.asyncTheme,onLoad:function(){oWidget.fireEvent('loadThemeEnd');oWidget.themeLoaded=true;oWidget.hideLoader();},onError:function(oError){oWidget.fireEvent('loadThemeEnd');oWidget.fireEvent('loadThemeError',oError);oWidget.themeLoaded=true;oWidget.hideLoader();}});}}
Zapatec.Widget.prototype.getClassName=function(oArg){var aClassName=[];if(oArg&&oArg.prefix){aClassName.push(oArg.prefix);}
if(this.config.theme!=''){aClassName.push(this.config.theme.charAt(0).toUpperCase());aClassName.push(this.config.theme.substr(1));}
if(oArg&&oArg.suffix){aClassName.push(oArg.suffix);}
return aClassName.join('');};Zapatec.Widget.prototype.formElementId=function(oArg){var aId=[];if(oArg&&oArg.prefix){aId.push(oArg.prefix);}else{aId.push('zpWidget');}
aId.push(this.id);if(oArg&&oArg.suffix){aId.push(oArg.suffix);}else{aId.push('-');}
if(typeof this.widgetUniqueIdCounter=='undefined'){this.widgetUniqueIdCounter=0;}else{this.widgetUniqueIdCounter++;}
aId.push(this.widgetUniqueIdCounter);return aId.join('');};Zapatec.Widget.prototype.showLoader=function(message){if(this.container!=null&&this.config.theme&&!this.themeLoaded){if(!Zapatec.windowLoaded){var self=this;Zapatec.Utils.addEvent(window,"load",function(){self.showLoader(message)});return null;}
if(typeof(Zapatec.Indicator)=='undefined'){var self=this;Zapatec.Transport.loadJS({module:'indicator',onLoad:function(){if(self.themeLoaded){return null;}
self.showLoader(message);}});return null;}
this.loader=new Zapatec.Indicator({container:this.container,themePath:Zapatec.zapatecPath+"../zpextra/themes/indicator/"});this.loader.start(message||'loading');this.container.style.visibility='hidden';}}
Zapatec.Widget.prototype.hideLoader=function(){if(this.loader&&this.loader.isActive()){this.container.style.visibility='';this.loader.stop();}}
Zapatec.Widget.prototype.showContainer=function(effects,animSpeed,onFinish){return this.showHideContainer(effects,animSpeed,onFinish,true);}
Zapatec.Widget.prototype.hideContainer=function(effects,animSpeed,onFinish){return this.showHideContainer(effects,animSpeed,onFinish,false);}
Zapatec.Widget.prototype.showHideContainer=function(effects,animSpeed,onFinish,show){if(this.container==null){return null;}
if(effects&&effects.length>0&&typeof(Zapatec.Effects)=='undefined'){var self=this;Zapatec.Transport.loadJS({url:Zapatec.zapatecPath+'../zpeffects/src/effects.js',onLoad:function(){self.showHideContainer(effects,animSpeed,onFinish,show);}});return false;}
if(animSpeed==null&&isNaN(parseInt(animSpeed))){animSpeed=5;}
if(!effects||effects.length==0){if(show){this.container.style.display=this.originalContainerDisplay;this.originalContainerDisplay=null;}else{this.originalContainerDisplay=this.container.style.display;this.container.style.display='none';}
if(onFinish){onFinish();}}else{if(show){Zapatec.Effects.show(this.container,animSpeed,effects,onFinish);}else{Zapatec.Effects.hide(this.container,animSpeed,effects,onFinish);}}
return true;}
Zapatec.Widget.prototype.loadData=function(oArg){if(typeof this.config.callbackSource=='function'){var oSource=this.config.callbackSource(oArg);if(oSource){if(typeof oSource.source!='undefined'){this.config.source=oSource.source;}
if(typeof oSource.sourceType!='undefined'){this.config.sourceType=oSource.sourceType;}}}
if(this.config.source!=null&&this.config.sourceType!=null){var sSourceType=this.config.sourceType.toLowerCase();if(sSourceType=='html'){this.fireEvent('loadDataStart');this.loadDataHtml(Zapatec.Widget.getElementById(this.config.source));this.fireEvent('loadDataEnd');}else if(sSourceType=='html/text'){this.fireEvent('loadDataStart');this.loadDataHtmlText(this.config.source);this.fireEvent('loadDataEnd');}else if(sSourceType=='html/url'){this.fireEvent('fetchSourceStart');var oWidget=this;Zapatec.Transport.fetch({url:this.config.source,async:this.config.asyncSource,onLoad:function(oRequest){oWidget.fireEvent('fetchSourceEnd');oWidget.fireEvent('loadDataStart');oWidget.loadDataHtmlText(oRequest.responseText);oWidget.fireEvent('loadDataEnd');},onError:function(oError){oWidget.fireEvent('fetchSourceError',oError);oWidget.fireEvent('fetchSourceEnd');oWidget.fireEvent('loadDataEnd');}});}else if(sSourceType=='json'){this.fireEvent('loadDataStart');if(typeof this.config.source=='object'){this.loadDataJson(this.config.source);}else if(this.config.reliableSource){this.loadDataJson(eval('('+this.config.source+')'));}else{this.loadDataJson(Zapatec.Transport.parseJson({strJson:this.config.source}));}
this.fireEvent('loadDataEnd');}else if(sSourceType=='json/url'){this.fireEvent('fetchSourceStart');var oWidget=this;Zapatec.Transport.fetchJsonObj({url:this.config.source,async:this.config.asyncSource,reliable:this.config.reliableSource,onLoad:function(oResult){oWidget.fireEvent('fetchSourceEnd');oWidget.fireEvent('loadDataStart');oWidget.loadDataJson(oResult);oWidget.fireEvent('loadDataEnd');},onError:function(oError){oWidget.fireEvent('fetchSourceError',oError);oWidget.fireEvent('fetchSourceEnd');oWidget.fireEvent('loadDataEnd');}});}else if(sSourceType=='xml'){this.fireEvent('loadDataStart');if(typeof this.config.source=='object'){this.loadDataXml(this.config.source);}else{this.loadDataXml(Zapatec.Transport.parseXml({strXml:this.config.source}));}
this.fireEvent('loadDataEnd');}else if(sSourceType=='xml/url'){this.fireEvent('fetchSourceStart');var oWidget=this;Zapatec.Transport.fetchXmlDoc({url:this.config.source,async:this.config.asyncSource,onLoad:function(oResult){oWidget.fireEvent('fetchSourceEnd');oWidget.fireEvent('loadDataStart');oWidget.loadDataXml(oResult);oWidget.fireEvent('loadDataEnd');},onError:function(oError){oWidget.fireEvent('fetchSourceError',oError);oWidget.fireEvent('fetchSourceEnd');oWidget.fireEvent('loadDataEnd');}});}}else{this.fireEvent('loadDataStart');this.loadDataHtml(Zapatec.Widget.getElementById(this.config.source));this.fireEvent('loadDataEnd');}};Zapatec.Widget.prototype.loadDataHtml=function(oSource){};Zapatec.Widget.prototype.loadDataHtmlText=function(sSource){var oTempContainer=Zapatec.Transport.parseHtml(sSource);this.loadDataHtml(oTempContainer.firstChild);};Zapatec.Widget.prototype.loadDataJson=function(oSource){};Zapatec.Widget.prototype.loadDataXml=function(oSource){};Zapatec.Widget.prototype.editData=function(oArg){this.fireEvent('editData',oArg);};Zapatec.Widget.prototype.editDataGet=function(){return null;};Zapatec.Widget.prototype.editDataCancel=function(){this.fireEvent('editDataCancel');if(typeof this.hide=='function'){this.hide();}};Zapatec.Widget.prototype.editDataReturn=function(oArg){this.fireEvent('editDataReturn',oArg);if(!oArg.widget||typeof oArg.widget.editDataReceive!='function'){return;}
oArg.widget.editDataReceive({data:this.editDataGet()});this.editDataCancel();};Zapatec.Widget.prototype.editDataReceive=function(oArg){this.fireEvent('editDataReceive',oArg);};Zapatec.Widget.callMethod=function(iWidgetId,sMethod){var oWidget=Zapatec.Widget.getWidgetById(iWidgetId);if(oWidget&&typeof oWidget[sMethod]=='function'){var aArgs=[].slice.call(arguments,2);return oWidget[sMethod].apply(oWidget,aArgs);}};Zapatec.Widget.getElementById=function(element){if(typeof element=='string'){return document.getElementById(element);}
return element;};Zapatec.Widget.getStyle=function(element){var style=element.getAttribute('style')||'';if(typeof style=='string'){return style;}
return style.cssText;};Zapatec.Drag={};Zapatec.Utils.emulateWindowEvent(['mousedown','mousemove','mouseup']);Zapatec.Drag.currentId=null;Zapatec.Drag.start=function(oEv,sId,oArg){if(Zapatec.Drag.currentId){return true;}
var oEl=document.getElementById(sId);if(!oEl||oEl.zpDrag){return true;}
if(!oArg){oArg={};}
var oPos=Zapatec.Utils.getMousePos(oEv||window.event);Zapatec.EventDriven.fireEvent('dragStart',{id:sId});oEl.zpDrag=true;oEl.zpDragPageX=oPos.pageX;oEl.zpDragPageY=oPos.pageY;if(oEl.offsetParent){var oPos=Zapatec.Utils.getElementOffset(oEl);var oPosParent=Zapatec.Utils.getElementOffset(oEl.offsetParent);oEl.zpDragLeft=oPos.left-oPosParent.left;oEl.zpDragTop=oPos.top-oPosParent.top;}else{oEl.zpDragLeft=oEl.offsetLeft;oEl.zpDragTop=oEl.offsetTop;}
oEl.zpDragPrevLeft=oEl.zpDragLeft;oEl.zpDragPrevTop=oEl.zpDragTop;oEl.zpDragV=oArg.vertical;oEl.zpDragH=oArg.horizontal;oEl.zpDragLimTop=typeof oArg.limitTop=='number'?oArg.limitTop:-Infinity;oEl.zpDragLimBot=typeof oArg.limitBottom=='number'?oArg.limitBottom:Infinity;oEl.zpDragLimLft=typeof oArg.limitLeft=='number'?oArg.limitLeft:-Infinity;oEl.zpDragLimRgh=typeof oArg.limitRight=='number'?oArg.limitRight:Infinity;Zapatec.Drag.currentId=sId;Zapatec.Utils.addEvent(document,'mousemove',Zapatec.Drag.move);Zapatec.Utils.addEvent(document,'mouseup',Zapatec.Drag.end);return true;};Zapatec.Drag.move=function(oEv){oEv||(oEv=window.event);if(!Zapatec.Drag.currentId){return Zapatec.Utils.stopEvent(oEv);}
var oEl=document.getElementById(Zapatec.Drag.currentId);if(!(oEl&&oEl.zpDrag)){return Zapatec.Utils.stopEvent(oEv);}
var oPos=Zapatec.Utils.getMousePos(oEv);var oOffset={id:Zapatec.Drag.currentId,startLeft:oEl.zpDragLeft,startTop:oEl.zpDragTop,prevLeft:oEl.zpDragPrevLeft,prevTop:oEl.zpDragPrevTop,left:0,top:0};if(!oEl.zpDragV){var iLeft=oEl.zpDragLeft+oPos.pageX-oEl.zpDragPageX;if(oEl.zpDragLimLft<=iLeft&&oEl.zpDragLimRgh>=iLeft){oEl.style.right='';oEl.style.left=iLeft+'px';oOffset.left=iLeft;oEl.zpDragPrevLeft=iLeft;}else{oOffset.left=oOffset.prevLeft;}}
if(!oEl.zpDragH){var iTop=oEl.zpDragTop+oPos.pageY-oEl.zpDragPageY;if(oEl.zpDragLimTop<=iTop&&oEl.zpDragLimBot>=iTop){oEl.style.bottom='';oEl.style.top=iTop+'px';oOffset.top=iTop;oEl.zpDragPrevTop=iTop;}else{oOffset.top=oOffset.prevTop;}}
Zapatec.EventDriven.fireEvent('dragMove',oOffset);return Zapatec.Utils.stopEvent(oEv);};Zapatec.Drag.end=function(oEv){oEv||(oEv=window.event);if(!Zapatec.Drag.currentId){return Zapatec.Utils.stopEvent(oEv);}
var oEl=document.getElementById(Zapatec.Drag.currentId);if(!(oEl&&oEl.zpDrag)){return Zapatec.Utils.stopEvent(oEv);}
Zapatec.Utils.removeEvent(document,'mousemove',Zapatec.Drag.move);Zapatec.Utils.removeEvent(document,'mouseup',Zapatec.Drag.end);var oOffset={id:Zapatec.Drag.currentId,startLeft:oEl.zpDragLeft,startTop:oEl.zpDragTop,left:oEl.zpDragPrevLeft,top:oEl.zpDragPrevTop};Zapatec.Drag.currentId=null;oEl.zpDrag=null;oEl.zpDragPageX=null;oEl.zpDragPageY=null;oEl.zpDragLeft=null;oEl.zpDragTop=null;oEl.zpDragPrevLeft=null;oEl.zpDragPrevTop=null;oEl.zpDragV=null;oEl.zpDragH=null;oEl.zpDragLimTop=null;oEl.zpDragLimBot=null;oEl.zpDragLimLft=null;oEl.zpDragLimRgh=null;Zapatec.EventDriven.fireEvent('dragEnd',oOffset);return Zapatec.Utils.stopEvent(oEv);};

/*
 *
 * Copyright (c) 2004-2005 by Zapatec, Inc.
 * http://www.zapatec.com
 * 1700 MLK Way, Berkeley, California,
 * 94709, U.S.A.
 * All rights reserved.
 *
 *
 */
if (!window.Zapatec || (Zapatec && !Zapatec.include)) {
    alert("You need to include zapatec.js file!");
}
else {
    Zapatec.calendarPath = Zapatec.getPath("Zapatec.CalendarWidget");
}
window.calendar = null;
/**< global object that remembers the calendar */ // initialize the preferences object; // embed it in a try/catch so we don't have any surprises try { Zapatec.Calendar.loadPrefs(); } catch(e) {};

Zapatec.Calendar = function(firstDayOfWeek, dateStr, onSelected, onClose) {
    this.bShowHistoryEvent = false;
    this.activeDiv = null;
    this.currentDateEl = null;
    this.getDateStatus = null;
    this.getDateToolTip = null;
    this.getDateText = null;
    this.timeout = null;
    this.onSelected = onSelected || null;
    this.onClose = onClose || null;
    this.onFDOW = null;
    this.dragging = false;
    this.hidden = false;
    this.minYear = 1970;
    this.maxYear = 2050;
    this.minMonth = 0;
    this.maxMonth = 11;
    this.dateFormat = Zapatec.Calendar.i18n("DEF_DATE_FORMAT");
    this.ttDateFormat = Zapatec.Calendar.i18n("TT_DATE_FORMAT");
    this.historyDateFormat = "%B %d, %Y";
    this.isPopup = true;
    this.weekNumbers = true;
    this.noGrab = false;
    if (Zapatec.Calendar.prefs.fdow || (Zapatec.Calendar.prefs.fdow == 0)) {
        this.firstDayOfWeek = parseInt(Zapatec.Calendar.prefs.fdow, 10);
    }
    else {
        var fd = 0;
        if (typeof firstDayOfWeek == "number") {
            fd = firstDayOfWeek;
        }
        else if (typeof Zapatec.Calendar._FD == 'number') {
            fd = Zapatec.Calendar._FD;
        }
        this.firstDayOfWeek = fd;
    }
    this.showsOtherMonths = false;
    this.dateStr = dateStr;
    this.showsTime = false;
    this.sortOrder = "asc";
    this.time24 = true;
    this.timeInterval = null;
    this.yearStep = 2;
    this.hiliteToday = true;
    this.multiple = null;
    this.table = null;
    this.element = null;
    this.tbody = new Array();
    this.firstdayname = null;
    this.monthsCombo = null;
    this.hilitedMonth = null;
    this.activeMonth = null;
    this.yearsCombo = null;
    this.hilitedYear = null;
    this.activeYear = null;
    this.histCombo = null;
    this.hilitedHist = null;
    this.dateClicked = false;
    this.numberMonths = 1;
    this.controlMonth = 1;
    this.vertical = false;
    this.monthsInRow = 1;
    this.titles = new Array();
    this.rowsOfDayNames = new Array();
    this.helpButton = true;
    this.disableFdowClick = true;
    this.disableDrag = false;
    this.yearNav = true;
    this.closeButton = true;
    Zapatec.Calendar._initSDN();
};
Zapatec.Calendar._initSDN = function() {
    if (typeof Zapatec.Calendar._TT._SDN == "undefined") {
        if (typeof Zapatec.Calendar._TT._SDN_len == "undefined")
            Zapatec.Calendar._TT._SDN_len = 3;
        var ar = [];
        for (var i = 8; i > 0;) {
            ar[--i] = Zapatec.Calendar._TT._DN[i].substr(0, Zapatec.Calendar._TT._SDN_len);
        }
        Zapatec.Calendar._TT._SDN = ar;
        if (typeof Zapatec.Calendar._TT._SMN_len == "undefined")
            Zapatec.Calendar._TT._SMN_len = 3;
        ar = [];
        for (var i = 12; i > 0;) {
            ar[--i] = Zapatec.Calendar._TT._MN[i].substr(0, Zapatec.Calendar._TT._SMN_len);
        }
        Zapatec.Calendar._TT._SMN = ar;
    }
    if (typeof Zapatec.Calendar._TT._AMPM == "undefined") {
        Zapatec.Calendar._TT._AMPM = {am:"am",pm:"pm"};
    }
};
Zapatec.Calendar.i18n = function(str, type) {
    var tr = '';
    if (!type) {
        if (Zapatec.Calendar._TT)
            tr = Zapatec.Calendar._TT[str];
        if (!tr && Zapatec.Calendar._TT_en)
            tr = Zapatec.Calendar._TT_en[str];
    }
    else switch (type) {case"dn":tr = Zapatec.Calendar._TT._DN[str];break;case"sdn":tr = Zapatec.Calendar._TT._SDN[str];break;case"mn":tr = Zapatec.Calendar._TT._MN[str];break;case"smn":tr = Zapatec.Calendar._TT._SMN[str];break;case"ampm":tr = Zapatec.Calendar._TT._AMPM[str];break;}
    if (!tr)tr = "" + str;
    return tr;
};
Zapatec.Calendar._C = null;
Zapatec.Calendar.prefs = {fdow:null,history:"",sortOrder:"asc",hsize:9};
Zapatec.Calendar.savePrefs = function() {
    Zapatec.Utils.writeCookie("ZP_CAL", Zapatec.Utils.makePref(this.prefs), null, '/', 30);
};
Zapatec.Calendar.loadPrefs = function() {
    var txt = Zapatec.Utils.getCookie("ZP_CAL"),tmp;
    if (txt) {
        tmp = Zapatec.Utils.loadPref(txt);
        if (tmp)
            Zapatec.Utils.mergeObjects(this.prefs, tmp);
    }
};
Zapatec.Calendar._add_evs = function(el) {
    var C = Zapatec.Calendar;
    Zapatec.Utils.addEvent(el, "mouseover", C.dayMouseOver);
    Zapatec.Utils.addEvent(el, "mousedown", C.dayMouseDown);
    Zapatec.Utils.addEvent(el, "mouseout", C.dayMouseOut);
    if (Zapatec.is_ie)
        Zapatec.Utils.addEvent(el, "dblclick", C.dayMouseDblClick);
};
Zapatec.Calendar._del_evs = function(el) {
    var C = this;
    Zapatec.Utils.removeEvent(el, "mouseover", C.dayMouseOver);
    Zapatec.Utils.removeEvent(el, "mousedown", C.dayMouseDown);
    Zapatec.Utils.removeEvent(el, "mouseout", C.dayMouseOut);
    if (Zapatec.is_ie)
        Zapatec.Utils.removeEvent(el, "dblclick", C.dayMouseDblClick);
};
Zapatec.Calendar.findMonth = function(el) {
    if (typeof el.month != "undefined") {
        return el;
    }
    else if (el.parentNode && typeof el.parentNode.month != "undefined") {
        return el.parentNode;
    }
    return null;
};
Zapatec.Calendar.findHist = function(el) {
    if (typeof el.histDate != "undefined") {
        return el;
    }
    else if (el.parentNode && typeof el.parentNode.histDate != "undefined") {
        return el.parentNode;
    }
    return null;
};
Zapatec.Calendar.findYear = function(el) {
    if (typeof el.year != "undefined") {
        return el;
    }
    else if (el.parentNode && typeof el.parentNode.year != "undefined") {
        return el.parentNode;
    }
    return null;
};
Zapatec.Calendar.showMonthsCombo = function() {
    var cal = Zapatec.Calendar._C;
    if (!cal) {
        return false;
    }
    var cd = cal.activeDiv;
    var mc = cal.monthsCombo;
    var date = cal.date,MM = cal.date.getMonth(),YY = cal.date.getFullYear(),min = (YY == cal.minYear),max = (YY == cal.maxYear);
    for (var i = mc.firstChild; i; i = i.nextSibling) {
        var m = i.month;
        Zapatec.Utils.removeClass(i, "hilite");
        Zapatec.Utils.removeClass(i, "active");
        Zapatec.Utils.removeClass(i, "disabled");
        i.disabled = false;
        if ((min && m < cal.minMonth) || (max && m > cal.maxMonth)) {
            Zapatec.Utils.addClass(i, "disabled");
            i.disabled = true;
        }
        if (m == MM)
            Zapatec.Utils.addClass(cal.activeMonth = i, "active");
    }
    var s = mc.style;
    s.display = "block";
    if (cd.navtype < 0)
        s.left = cd.offsetLeft + "px";
    else {
        var mcw = mc.offsetWidth;
        if (typeof mcw == "undefined")
            mcw = 50;
        s.left = (cd.offsetLeft + cd.offsetWidth - mcw) + "px";
    }
    s.top = (cd.offsetTop + cd.offsetHeight) + "px";
    cal.updateWCH(mc);
};
Zapatec.Calendar.showHistoryCombo = function() {
    var cal = Zapatec.Calendar._C,a,h,i,cd,hc,s,tmp,div;
    if (!cal)
        return false;
    hc = cal.histCombo;
    while (hc.firstChild)
        hc.removeChild(hc.lastChild);
    if (Zapatec.Calendar.prefs.history) {
        a = Zapatec.Calendar.prefs.history.split(/,/);
        i = 0;
        while (tmp = a[i++]) {
            tmp = tmp.split(/\//);
            h = Zapatec.Utils.createElement("div");
            h.className = Zapatec.is_ie ? "label-IEfix" : "label";
            h.histDate = new Date(parseInt(tmp[0], 10), parseInt(tmp[1], 10) - 1, parseInt(tmp[2], 10), tmp[3] ? parseInt(tmp[3], 10) : 0, tmp[4] ? parseInt(tmp[4], 10) : 0);
            h.appendChild(window.document.createTextNode(h.histDate.print(cal.historyDateFormat)));
            hc.appendChild(h);
            if (h.histDate.dateEqualsTo(cal.date))
                Zapatec.Utils.addClass(h, "active");
        }
    }
    cd = cal.activeDiv;
    s = hc.style;
    s.display = "block";
    s.left = Math.floor(cd.offsetLeft + (cd.offsetWidth - hc.offsetWidth) / 2) + "px";
    s.top = (cd.offsetTop + cd.offsetHeight) + "px";
    cal.updateWCH(hc);
    cal.bEventShowHistory = true;
};
Zapatec.Calendar.showYearsCombo = function(fwd) {
    var cal = Zapatec.Calendar._C;
    if (!cal) {
        return false;
    }
    var cd = cal.activeDiv;
    var yc = cal.yearsCombo;
    if (cal.hilitedYear) {
        Zapatec.Utils.removeClass(cal.hilitedYear, "hilite");
    }
    if (cal.activeYear) {
        Zapatec.Utils.removeClass(cal.activeYear, "active");
    }
    cal.activeYear = null;
    var Y = cal.date.getFullYear() + (fwd ? 1 : -1);
    var yr = yc.firstChild;
    var show = false;
    for (var i = 12; i > 0; --i) {
        if (Y >= cal.minYear && Y <= cal.maxYear) {
            yr.firstChild.data = Y;
            yr.year = Y;
            yr.style.display = "block";
            show = true;
        }
        else {
            yr.style.display = "none";
        }
        yr = yr.nextSibling;
        Y += fwd ? cal.yearStep : -cal.yearStep;
    }
    if (show) {
        var s = yc.style;
        s.display = "block";
        if (cd.navtype < 0)
            s.left = cd.offsetLeft + "px";
        else {
            var ycw = yc.offsetWidth;
            if (typeof ycw == "undefined")
                ycw = 50;
            s.left = (cd.offsetLeft + cd.offsetWidth - ycw) + "px";
        }
        s.top = (cd.offsetTop + cd.offsetHeight) + "px";
    }
    cal.updateWCH(yc);
};
Zapatec.Calendar.tableMouseUp = function(ev) {
    var cal = Zapatec.Calendar._C;
    if (!cal) {
        return false;
    }
    if (cal.timeout) {
        clearTimeout(cal.timeout);
    }
    var el = cal.activeDiv;
    if (!el) {
        return false;
    }
    var target = Zapatec.Utils.getTargetElement(ev);
    if (typeof(el.navtype) == "undefined") {
        while (target && !target.calendar) {
            target = target.parentNode;
        }
    }
    ev || (ev = window.event);
    Zapatec.Utils.removeClass(el, "active");
    if (target == el || target.parentNode == el) {
        Zapatec.Calendar.cellClick(el, ev);
    }
    var mon = Zapatec.Calendar.findMonth(target);
    var date = null;
    if (mon) {
        if (!mon.disabled) {
            date = new Date(cal.date);
            if (mon.month != date.getMonth()) {
                date.setMonth(mon.month);
                cal.setDate(date, true);
                cal.dateClicked = false;
                cal.callHandler();
            }
        }
    }
    else {
        var year = Zapatec.Calendar.findYear(target);
        if (year) {
            date = new Date(cal.date);
            if (year.year != date.getFullYear()) {
                date.setFullYear(year.year);
                cal.setDate(date, true);
                cal.dateClicked = false;
                cal.callHandler();
            }
        }
        else {
            var hist = Zapatec.Calendar.findHist(target);
            if (hist && !hist.histDate.dateEqualsTo(cal.date)) {
                date = new Date(hist.histDate);
                cal._init(cal.firstDayOfWeek, cal.date = date);
                cal.dateClicked = false;
                cal.callHandler();
                cal.updateHistory();
            }
        }
    }
    Zapatec.Utils.removeEvent(window.document, "mouseup", Zapatec.Calendar.tableMouseUp);
    Zapatec.Utils.removeEvent(window.document, "mouseover", Zapatec.Calendar.tableMouseOver);
    Zapatec.Utils.removeEvent(window.document, "mousemove", Zapatec.Calendar.tableMouseOver);
    cal._hideCombos();
    Zapatec.Calendar._C = null;
    return Zapatec.Utils.stopEvent(ev);
};
Zapatec.Calendar.tableMouseOver = function(ev) {
    var cal = Zapatec.Calendar._C;
    if (!cal) {
        return;
    }
    var el = cal.activeDiv;
    var target = Zapatec.Utils.getTargetElement(ev);
    if (target == el || target.parentNode == el) {
        Zapatec.Utils.addClass(el, "hilite active");
        Zapatec.Utils.addClass(el.parentNode, "rowhilite");
    }
    else {
        if (typeof el.navtype == "undefined" || (el.navtype != 50 && ((el.navtype == 0 && !cal.histCombo) || Math.abs(el.navtype) > 2)))
            Zapatec.Utils.removeClass(el, "active");
        Zapatec.Utils.removeClass(el, "hilite");
        Zapatec.Utils.removeClass(el.parentNode, "rowhilite");
    }
    ev || (ev = window.event);
    if (el.navtype == 50 && target != el) {
        var pos = Zapatec.Utils.getAbsolutePos(el);
        var w = el.offsetWidth;
        var x = ev.clientX;
        var dx;
        var decrease = true;
        if (x > pos.x + w) {
            dx = x - pos.x - w;
            decrease = false;
        }
        else
            dx = pos.x - x;
        if (dx < 0)dx = 0;
        var range = el._range;
        var current = el._current;
        var date = cal.currentDate;
        var pm = (date.getHours() >= 12);
        var old = el.firstChild.data;
        var count = Math.floor(dx / 10) % range.length;
        for (var i = range.length; --i >= 0;)
            if (range[i] == current)
                break;
        while (count-- > 0)
            if (decrease) {
                if (--i < 0) {
                    i = range.length - 1;
                }
            }
            else if (++i >= range.length) {
                i = 0;
            }
        if (cal.getDateStatus) {
            var minute = null;
            var hour = null;
            var new_date = new Date(date);
            if (el.className.indexOf("ampm", 0) != -1) {
                minute = date.getMinutes();
                if (old != range[i]) {
                    hour = (range[i] == Zapatec.Calendar.i18n("pm", "ampm")) ? ((date.getHours() == 0) ? (12) : (date.getHours() + 12)) : (date.getHours() - 12);
                }
                else {
                    hour = date.getHours();
                }
                new_date.setHours(hour);
            }
            if (el.className.indexOf("hour", 0) != -1) {
                minute = date.getMinutes();
                hour = (!cal.time24) ? ((pm) ? ((range[i] != 12) ? (parseInt(range[i], 10) + 12) : (12)) : ((range[i] != 12) ? (range[i]) : (0))) : (range[i]);
                new_date.setHours(hour);
            }
            if (el.className.indexOf("minute", 0) != -1) {
                hour = date.getHours();
                minute = range[i];
                new_date.setMinutes(minute);
            }
        }
        var status = false;
        if (cal.getDateStatus) {
            status = cal.getDateStatus(new_date, date.getFullYear(), date.getMonth(), date.getDate(), parseInt(hour, 10), parseInt(minute, 10));
        }
        if (status == false) {
            if (!((!cal.time24) && (range[i] == Zapatec.Calendar.i18n("pm", "ampm")) && (hour > 23))) {
                el.firstChild.data = range[i];
            }
        }
        cal.onUpdateTime();
    }
    var mon = Zapatec.Calendar.findMonth(target);
    if (mon) {
        if (!mon.disabled) {
            if (mon.month != cal.date.getMonth()) {
                if (cal.hilitedMonth) {
                    Zapatec.Utils.removeClass(cal.hilitedMonth, "hilite");
                }
                Zapatec.Utils.addClass(mon, "hilite");
                cal.hilitedMonth = mon;
            }
            else if (cal.hilitedMonth) {
                Zapatec.Utils.removeClass(cal.hilitedMonth, "hilite");
            }
        }
    }
    else {
        if (cal.hilitedMonth) {
            Zapatec.Utils.removeClass(cal.hilitedMonth, "hilite");
        }
        var year = Zapatec.Calendar.findYear(target);
        if (year) {
            if (year.year != cal.date.getFullYear()) {
                if (cal.hilitedYear) {
                    Zapatec.Utils.removeClass(cal.hilitedYear, "hilite");
                }
                Zapatec.Utils.addClass(year, "hilite");
                cal.hilitedYear = year;
            }
            else if (cal.hilitedYear) {
                Zapatec.Utils.removeClass(cal.hilitedYear, "hilite");
            }
        }
        else {
            if (cal.hilitedYear) {
                Zapatec.Utils.removeClass(cal.hilitedYear, "hilite");
            }
            var hist = Zapatec.Calendar.findHist(target);
            if (hist) {
                if (!hist.histDate.dateEqualsTo(cal.date)) {
                    if (cal.hilitedHist) {
                        Zapatec.Utils.removeClass(cal.hilitedHist, "hilite");
                    }
                    Zapatec.Utils.addClass(hist, "hilite");
                    cal.hilitedHist = hist;
                }
                else if (cal.hilitedHist) {
                    Zapatec.Utils.removeClass(cal.hilitedHist, "hilite");
                }
            }
            else if (cal.hilitedHist) {
                Zapatec.Utils.removeClass(cal.hilitedHist, "hilite");
            }
        }
    }
    return Zapatec.Utils.stopEvent(ev);
};
Zapatec.Calendar.tableMouseDown = function(ev) {
    if (Zapatec.Utils.getTargetElement(ev) == Zapatec.Utils.getElement(ev)) {
        return Zapatec.Utils.stopEvent(ev);
    }
};
Zapatec.Calendar.calDragIt = function(ev) {
    ev || (ev = window.event);
    var cal = Zapatec.Calendar._C;
    if (!cal) {
        Zapatec.Calendar.calDragEnd();
    }
    if (!cal.disableDrag) {
        if (!(cal && cal.dragging)) {
            return false;
        }
        var posX = ev.clientX + window.document.body.scrollLeft;
        var posY = ev.clientY + window.document.body.scrollTop;
        cal.hideShowCovered();
        var st = cal.element.style,L = posX - cal.xOffs,T = posY - cal.yOffs;
        st.left = L + "px";
        st.top = T + "px";
        Zapatec.Utils.setupWCH(cal.WCH, L, T);
    }
    return Zapatec.Utils.stopEvent(ev);
};
Zapatec.Calendar.calDragEnd = function(ev) {
    var cal = Zapatec.Calendar._C;
    Zapatec.Utils.removeEvent(window.document, "mousemove", Zapatec.Calendar.calDragIt);
    Zapatec.Utils.removeEvent(window.document, "mouseover", Zapatec.Calendar.calDragIt);
    Zapatec.Utils.removeEvent(window.document, "mouseup", Zapatec.Calendar.calDragEnd);
    if (!cal) {
        return false;
    }
    cal.dragging = false;
    Zapatec.Calendar.tableMouseUp(ev);
    cal.hideShowCovered();
};
Zapatec.Calendar.dayMouseDown = function(ev) {
    var canDrag = true;
    var el = Zapatec.Utils.getElement(ev);
    if (el.className.indexOf("disabled") != -1 || el.className.indexOf("true") != -1) {
        return false;
    }
    var cal = el.calendar;
    while (!cal) {
        el = el.parentNode;
        cal = el.calendar;
    }
    cal.bEventShowHistory = false;
    cal.activeDiv = el;
    Zapatec.Calendar._C = cal;
    if (el.navtype != 300) {
        if (el.navtype == 50) {
            if (!((cal.timeInterval == null) || ((cal.timeInterval < 60) && (el.className.indexOf("hour", 0) != -1)))) {
                canDrag = false;
            }
            el._current = el.firstChild.data;
            if (canDrag) {
                Zapatec.Utils.addEvent(window.document, "mousemove", Zapatec.Calendar.tableMouseOver);
            }
        }
        else {
            if (((el.navtype == 201) || (el.navtype == 202)) && (cal.timeInterval > 30) && (el.timePart.className.indexOf("minute", 0) != -1)) {
                canDrag = false;
            }
            if (canDrag) {
                Zapatec.Utils.addEvent(window.document, Zapatec.is_ie5 ? "mousemove" : "mouseover", Zapatec.Calendar.tableMouseOver);
            }
        }
        if (canDrag) {
            Zapatec.Utils.addClass(el, "hilite active");
        }
        Zapatec.Utils.addEvent(window.document, "mouseup", Zapatec.Calendar.tableMouseUp);
    }
    else if (cal.isPopup) {
        cal._dragStart(ev);
    }
    else {
        Zapatec.Calendar._C = null;
    }
    if (el.navtype == -1 || el.navtype == 1) {
        if (cal.timeout)clearTimeout(cal.timeout);
        cal.timeout = setTimeout("Zapatec.Calendar.showMonthsCombo()", 250);
    }
    else if (el.navtype == -2 || el.navtype == 2) {
        if (cal.timeout)clearTimeout(cal.timeout);
        cal.timeout = setTimeout((el.navtype > 0) ? "Zapatec.Calendar.showYearsCombo(true)" : "Zapatec.Calendar.showYearsCombo(false)", 250);
    }
    else if (el.navtype == 0 && Zapatec.Calendar.prefs.history) {
        if (cal.timeout)clearTimeout(cal.timeout);
        cal.timeout = setTimeout("Zapatec.Calendar.showHistoryCombo()", 250);
    }
    else {
        cal.timeout = null;
    }
    return Zapatec.Utils.stopEvent(ev);
};
Zapatec.Calendar.dayMouseDblClick = function(ev) {
    Zapatec.Calendar.cellClick(Zapatec.Utils.getElement(ev), ev || window.event);
    if (Zapatec.is_ie)
        window.document.selection.empty();
};
Zapatec.Calendar.dayMouseOver = function(ev) {
    var el = Zapatec.Utils.getElement(ev),caldate = el.caldate;
    while (!el.calendar) {
        el = el.parentNode;
        caldate = el.caldate;
    }
    var cal = el.calendar;
    var cel = el.timePart;
    if (caldate) {
        caldate = new Date(caldate[0], caldate[1], caldate[2]);
        if (caldate.getDate() != el.caldate[2])caldate.setDate(el.caldate[2]);
    }
    if (Zapatec.Utils.isRelated(el, ev) || Zapatec.Calendar._C || el.className.indexOf("disabled") != -1 || el.className.indexOf("true") != -1) {
        return false;
    }
    if (el.ttip) {
        if (el.ttip.substr(0, 1) == "_") {
            el.ttip = caldate.print(el.calendar.ttDateFormat) + el.ttip.substr(1);
        }
        el.calendar.showHint(el.ttip);
    }
    if (el.navtype != 300) {
        if (!((cal.timeInterval == null) || (el.className.indexOf("ampm", 0) != -1) || ((cal.timeInterval < 60) && (el.className.indexOf("hour", 0) != -1))) && (el.navtype == 50)) {
            return Zapatec.Utils.stopEvent(ev);
        }
        if (((el.navtype == 201) || (el.navtype == 202)) && (cal.timeInterval > 30) && (cel.className.indexOf("minute", 0) != -1)) {
            return Zapatec.Utils.stopEvent(ev);
        }
        Zapatec.Utils.addClass(el, "hilite");
        if (caldate) {
            Zapatec.Utils.addClass(el.parentNode, "rowhilite");
        }
    }
    return Zapatec.Utils.stopEvent(ev);
};
Zapatec.Calendar.dayMouseOut = function(ev) {
    var el = Zapatec.Utils.getElement(ev);
    while (!el.calendar) {
        el = el.parentNode;
        caldate = el.caldate;
    }
    if (Zapatec.Utils.isRelated(el, ev) || Zapatec.Calendar._C || el.className.indexOf("disabled") != -1 || el.className.indexOf("true") != -1)
        return false;
    Zapatec.Utils.removeClass(el, "hilite");
    if (el.caldate)
        Zapatec.Utils.removeClass(el.parentNode, "rowhilite");
    if (el.calendar)
        el.calendar.showHint(Zapatec.Calendar.i18n("SEL_DATE"));
    return Zapatec.Utils.stopEvent(ev);
};
Zapatec.Calendar.cellClick = function(el, ev) {
    var cal = el.calendar;
    var closing = false;
    var newdate = false;
    var date = null;
    while (!cal) {
        el = el.parentNode;
        cal = el.calendar;
    }
    if (el.className.indexOf("disabled") != -1 || el.className.indexOf("true") != -1) {
        return false;
    }
    if (typeof el.navtype == "undefined") {
        if (cal.currentDateEl) {
            Zapatec.Utils.removeClass(cal.currentDateEl, "selected");
            Zapatec.Utils.addClass(el, "selected");
            closing = (cal.currentDateEl == el);
            if (!closing) {
                cal.currentDateEl = el;
            }
        }
        var tmpDate = new Date(el.caldate[0], el.caldate[1], el.caldate[2]);
        if (tmpDate.getDate() != el.caldate[2]) {
            tmpDate.setDate(el.caldate[2]);
        }
        cal.date.setDateOnly(tmpDate);
        cal.currentDate.setDateOnly(tmpDate);
        date = cal.date;
        cal.dateClicked = true;
        if (cal.multiple)
            cal._toggleMultipleDate(new Date(date));
        newdate = true;
        if (el.otherMonth)
            cal._init(cal.firstDayOfWeek, date);
        cal.onSetTime();
    }
    else {
        if (el.navtype == 200) {
            Zapatec.Utils.removeClass(el, "hilite");
            cal.callCloseHandler();
            return;
        }
        date = new Date(cal.date);
        if (el.navtype == 0 && !cal.bEventShowHistory)
            date.setDateOnly(new Date());
        cal.dateClicked = false;
        var year = date.getFullYear();
        var mon = date.getMonth();
        function setMonth(m) {
            var day = date.getDate();
            var max = date.getMonthDays(m);
            if (day > max) {
                date.setDate(max);
            }
            date.setMonth(m);
        }
        ;
        switch (el.navtype) {case 400:Zapatec.Utils.removeClass(el, "hilite");var text = Zapatec.Calendar.i18n("ABOUT");if (typeof text != "undefined") {
            text += cal.showsTime ? Zapatec.Calendar.i18n("ABOUT_TIME") : "";
        }
        else {
            text = "Help and about box text is not translated into this language.\n" + "If you know this language and you feel generous please update\n" + "the corresponding file in \"lang\" subdir to match calendar-en.js\n" + "and send it back to <support@zapatec.com> to get it into the distribution  ;-)\n\n" + "Thank you!\n" + "http://www.zapatec.com\n";
        }
            alert(text);return;case-2:if (year > cal.minYear) {
            date.setFullYear(year - 1);
        }
            break;case-1:if (mon > 0) {
            setMonth(mon - 1);
        }
        else if (year-- > cal.minYear) {
            date.setFullYear(year);
            setMonth(11);
        }
            break;case 1:if (mon < 11) {
            setMonth(mon + 1);
        }
        else if (year < cal.maxYear) {
            date.setFullYear(year + 1);
            setMonth(0);
        }
            break;case 2:if (year < cal.maxYear) {
            date.setFullYear(year + 1);
        }
            break;case 100:cal.setFirstDayOfWeek(el.fdow);Zapatec.Calendar.prefs.fdow = cal.firstDayOfWeek;Zapatec.Calendar.savePrefs();if (cal.onFDOW)
            cal.onFDOW(cal.firstDayOfWeek);return;case 50:var date = cal.currentDate;if (el.className.indexOf("ampm", 0) >= 0);
        else
            if (!((cal.timeInterval == null) || ((cal.timeInterval < 60) && (el.className.indexOf("hour", 0) != -1)))) {
                break;
            }
            var range = el._range;var current = el.firstChild.data;var pm = (date.getHours() >= 12);for (var i = range.length; --i >= 0;)
            if (range[i] == current)
                break;if (ev && ev.shiftKey) {
            if (--i < 0) {
                i = range.length - 1;
            }
        }
        else if (++i >= range.length) {
            i = 0;
        }
            if (cal.getDateStatus) {
                var minute = null;
                var hour = null;
                var new_date = new Date(date);
                if (el.className.indexOf("ampm", 0) != -1) {
                    minute = date.getMinutes();
                    hour = (range[i] == Zapatec.Calendar.i18n("pm", "ampm")) ? ((date.getHours() == 12) ? (date.getHours()) : (date.getHours() + 12)) : (date.getHours() - 12);
                    if (cal.getDateStatus && cal.getDateStatus(new_date, date.getFullYear(), date.getMonth(), date.getDate(), parseInt(hour, 10), parseInt(minute, 10))) {
                        var dirrect;
                        if (range[i] == Zapatec.Calendar.i18n("pm", "ampm")) {
                            dirrect = -5;
                        }
                        else {
                            dirrect = 5;
                        }
                        hours = hour;
                        minutes = minute;
                        do{
                            minutes += dirrect;
                            if (minutes >= 60) {
                                minutes -= 60;
                                ++hours;
                                if (hours >= 24)hours -= 24;
                                new_date.setHours(hours);
                            }
                            if (minutes < 0) {
                                minutes += 60;
                                --hours;
                                if (hours < 0)hours += 24;
                                new_date.setHours(hours);
                            }
                            new_date.setMinutes(minutes);
                            if (!cal.getDateStatus(new_date, date.getFullYear(), date.getMonth(), date.getDate(), parseInt(hours, 10), parseInt(minutes, 10))) {
                                hour = hours;
                                minute = minutes;
                                if (hour > 12)i = 1;
                                else i = 0;
                                cal.date.setHours(hour);
                                cal.date.setMinutes(minute);
                                cal.onSetTime();
                            }
                        }
                        while ((hour != hours) || (minute != minutes));
                    }
                    new_date.setHours(hour);
                }
                if (el.className.indexOf("hour", 0) != -1) {
                    minute = date.getMinutes();
                    hour = (!cal.time24) ? ((pm) ? ((range[i] != 12) ? (parseInt(range[i], 10) + 12) : (12)) : ((range[i] != 12) ? (range[i]) : (0))) : (range[i]);
                    new_date.setHours(hour);
                }
                if (el.className.indexOf("minute", 0) != -1) {
                    hour = date.getHours();
                    minute = range[i];
                    new_date.setMinutes(minute);
                }
            }
            var status = false;if (cal.getDateStatus) {
            status = cal.getDateStatus(new_date, date.getFullYear(), date.getMonth(), date.getDate(), parseInt(hour, 10), parseInt(minute, 10));
        }
            if (!status) {
                el.firstChild.data = range[i];
            }
            cal.onUpdateTime();return;case 201:case 202:var cel = el.timePart;var date = cal.currentDate;if ((cel.className.indexOf("minute", 0) != -1) && (cal.timeInterval > 30)) {
            break;
        }
            var val = parseInt(cel.firstChild.data, 10);var pm = (date.getHours() >= 12);var range = cel._range;for (var i = range.length; --i >= 0;)
            if (val == range[i]) {
                val = i;
                break;
            }
            var step = cel._step;if (el.navtype == 201) {
            val = step * Math.floor(val / step);
            val += step;
            if (val >= range.length)
                val = 0;
        }
        else {
            val = step * Math.ceil(val / step);
            val -= step;
            if (val < 0)
                val = range.length - step;
        }
            if (cal.getDateStatus) {
                var minute = null;
                var hour = null;
                var new_date = new Date(date);
                if (cel.className == "hour") {
                    minute = date.getMinutes();
                    hour = (!cal.time24) ? ((pm) ? ((range[val] != 12) ? (parseInt(range[val], 10) + 12) : (12)) : ((range[val] != 12) ? (range[val]) : (0))) : (range[val]);
                    new_date.setHours(hour);
                }
                if (cel.className == "minute") {
                    hour = date.getHours();
                    minute = val;
                    new_date.setMinutes(range[val]);
                }
            }
            var status = false;if (cal.getDateStatus) {
            status = cal.getDateStatus(new_date, date.getFullYear(), date.getMonth(), date.getDate(), parseInt(hour, 10), parseInt(minute, 10));
        }
            if (!status) {
                cel.firstChild.data = range[val];
            }
            cal.onUpdateTime();return;case 0:if (cal.getDateStatus && ((cal.getDateStatus(date, date.getFullYear(), date.getMonth(), date.getDate()) == true) || (cal.getDateStatus(date, date.getFullYear(), date.getMonth(), date.getDate()) == "disabled"))) {
            return false;
        }
            break;}
        if (!date.equalsTo(cal.date)) {
            if ((el.navtype >= -2 && el.navtype <= 2) && (el.navtype != 0)) {
                cal._init(cal.firstDayOfWeek, date, true);
                return;
            }
            cal.setDate(date);
            newdate = !(el.navtype && (el.navtype >= -2 && el.navtype <= 2));
        }
    }
    if (newdate) {
        cal.callHandler();
    }
    if (closing) {
        Zapatec.Utils.removeClass(el, "hilite");
        cal.callCloseHandler();
    }
};
Zapatec.Calendar.prototype.create = function(_par) {
    var parent = null;
    if (!_par) {
        parent = window.document.getElementsByTagName("body")[0];
        this.isPopup = true;
        this.WCH = Zapatec.Utils.createWCH();
    }
    else {
        parent = _par;
        this.isPopup = false;
    }
    this.currentDate = this.date = this.dateStr ? new Date(this.dateStr) : new Date();
    var table = Zapatec.Utils.createElement("table");
    this.table = table;
    table.cellSpacing = 0;
    table.cellPadding = 0;
    Zapatec.Utils.createProperty(table, "calendar", this);
    Zapatec.Utils.addEvent(table, "mousedown", Zapatec.Calendar.tableMouseDown);
    var div = Zapatec.Utils.createElement("div");
    this.element = div;
    div.className = "calendar";
    if (Zapatec.is_opera) {
        table.style.width = (this.monthsInRow * ((this.weekNumbers) ? (8) : (7)) * 2 + 4.4 * this.monthsInRow) + "em";
    }
    if (this.isPopup) {
        div.style.position = "absolute";
        div.style.display = "none";
    }
    div.appendChild(table);
    var cell = null;
    var row = null;
    var cal = this;
    var hh = function(text, cs, navtype) {
        cell = Zapatec.Utils.createElement("td", row);
        cell.colSpan = cs;
        cell.className = "button";
        if (Math.abs(navtype) <= 2)
            cell.className += " nav";
        Zapatec.Calendar._add_evs(cell);
        Zapatec.Utils.createProperty(cell, "calendar", cal);
        cell.navtype = navtype;
        if (text.substr(0, 1) != "&") {
            cell.appendChild(document.createTextNode(text));
        }
        else {
            cell.innerHTML = text;
        }
        return cell;
    };
    var hd = function(par, colspan) {
        cell = Zapatec.Utils.createElement("td", par);
        cell.colSpan = colspan;
        cell.className = "button";
        cell.innerHTML = "<div>&nbsp</div>";
        return cell;
    };
    var title_length = ((this.weekNumbers) ? (8) : (7)) * this.monthsInRow - 2;
    var thead = Zapatec.Utils.createElement("thead", table);
    if (this.numberMonths == 1) {
        this.title = thead;
    }
    row = Zapatec.Utils.createElement("tr", thead);
    if (this.helpButton) {
        hh("?", 1, 400).ttip = Zapatec.Calendar.i18n("INFO");
    }
    else {
        hd(row, 1);
    }
    this.title = hh("&nbsp;", title_length, 300);
    this.title.className = "title";
    if (this.isPopup) {
        if (!this.disableDrag) {
            this.title.ttip = Zapatec.Calendar.i18n("DRAG_TO_MOVE");
            this.title.style.cursor = "move";
        }
        if (this.closeButton) {
            hh("&#x00d7;", 1, 200).ttip = Zapatec.Calendar.i18n("CLOSE");
        }
        else {
            hd(row, 1);
        }
    }
    else {
        hd(row, 1);
    }
    row = Zapatec.Utils.createElement("tr", thead);
    this._nav_py = hh("&#x00ab;", 1, -2);
    this._nav_py.ttip = Zapatec.Calendar.i18n("PREV_YEAR");
    this._nav_pm = hh("&#x2039;", 1, -1);
    this._nav_pm.ttip = Zapatec.Calendar.i18n("PREV_MONTH");
    this._nav_now = hh(Zapatec.Calendar.i18n("TODAY"), title_length - 2, 0);
    this._nav_now.ttip = Zapatec.Calendar.i18n("GO_TODAY");
    this._nav_nm = hh("&#x203a;", 1, 1);
    this._nav_nm.ttip = Zapatec.Calendar.i18n("NEXT_MONTH");
    this._nav_ny = hh("&#x00bb;", 1, 2);
    this._nav_ny.ttip = Zapatec.Calendar.i18n("NEXT_YEAR");
    var rowsOfMonths = Math.floor(this.numberMonths / this.monthsInRow);
    if (this.numberMonths % this.monthsInRow > 0) {
        ++rowsOfMonths;
    }
    for (var l = 1; l <= rowsOfMonths; ++l) {
        var thead = Zapatec.Utils.createElement("thead", table);
        if (Zapatec.is_opera) {
            thead.style.display = "table-row-group";
        }
        if (this.numberMonths != 1) {
            row = Zapatec.Utils.createElement("tr", thead);
            var title_length = 5;
            this.weekNumbers && ++title_length;
            this.titles[l] = new Array();
            for (var k = 1; (k <= this.monthsInRow) && ((l - 1) * this.monthsInRow + k <= this.numberMonths); ++k) {
                hd(row, 1);
                this.titles[l][k] = hh("&nbsp;", title_length, 300);
                this.titles[l][k].className = "title";
                hd(row, 1);
            }
        }
        row = Zapatec.Utils.createElement("tr", thead);
        row.className = "daynames";
        for (k = 1; (k <= this.monthsInRow) && ((l - 1) * this.monthsInRow + k <= this.numberMonths); ++k) {
            if (this.weekNumbers) {
                cell = Zapatec.Utils.createElement("td", row);
                cell.className = "name wn";
                cell.appendChild(window.document.createTextNode(Zapatec.Calendar.i18n("WK")));
                if (k > 1) {
                    Zapatec.Utils.addClass(cell, "month-left-border");
                }
                var cal_wk = Zapatec.Calendar.i18n("WK")
                if (cal_wk == null) {
                    cal_wk = "";
                }
            }
            for (var i = 7; i > 0; --i) {
                cell = Zapatec.Utils.createElement("td", row);
                cell.appendChild(document.createTextNode("&nbsp;"));
            }
        }
        this.firstdayname = row.childNodes[this.weekNumbers ? 1 : 0];
        this.rowsOfDayNames[l] = this.firstdayname;
        this._displayWeekdays();
        var tbody = Zapatec.Utils.createElement("tbody", table);
        this.tbody[l] = tbody;
        for (i = 6; i > 0; --i) {
            row = Zapatec.Utils.createElement("tr", tbody);
            for (k = 1; (k <= this.monthsInRow) && ((l - 1) * this.monthsInRow + k <= this.numberMonths); ++k) {
                if (this.weekNumbers) {
                    cell = Zapatec.Utils.createElement("td", row);
                    cell.appendChild(document.createTextNode("&nbsp;"));
                }
                for (var j = 7; j > 0; --j) {
                    cell = Zapatec.Utils.createElement("td", row);
                    cell.appendChild(document.createTextNode("&nbsp;"));
                    Zapatec.Utils.createProperty(cell, "calendar", this);
                    Zapatec.Calendar._add_evs(cell);
                }
            }
        }
    }
    var tfoot = Zapatec.Utils.createElement("tfoot", table);
    if (this.showsTime) {
        row = Zapatec.Utils.createElement("tr", tfoot);
        row.className = "time";
        var emptyColspan;
        if (this.monthsInRow != 1) {
            cell = Zapatec.Utils.createElement("td", row);
            emptyColspan = cell.colSpan = Math.ceil((((this.weekNumbers) ? 8 : 7) * (this.monthsInRow - 1)) / 2);
            cell.className = "timetext";
            cell.innerHTML = "&nbsp";
        }
        cell = Zapatec.Utils.createElement("td", row);
        cell.className = "timetext";
        cell.colSpan = this.weekNumbers ? 2 : 1;
        cell.innerHTML = Zapatec.Calendar.i18n("TIME") || "&nbsp;";
        (function() {
            function makeTimePart(className, init, range_start, range_end) {
                var table,tbody,tr,tr2,part;
                if (range_end) {
                    cell = Zapatec.Utils.createElement("td", row);
                    cell.colSpan = 1;
                    if (cal.showsTime != "seconds") {
                        ++cell.colSpan;
                    }
                    cell.className = "parent-" + className;
                    table = Zapatec.Utils.createElement("table", cell);
                    table.cellSpacing = table.cellPadding = 0;
                    if (className == "hour")
                        table.align = "right";
                    table.className = "calendar-time-scroller";
                    tbody = Zapatec.Utils.createElement("tbody", table);
                    tr = Zapatec.Utils.createElement("tr", tbody);
                    tr2 = Zapatec.Utils.createElement("tr", tbody);
                }
                else
                    tr = row;
                part = Zapatec.Utils.createElement("td", tr);
                part.className = className;
                part.appendChild(window.document.createTextNode(init));
                Zapatec.Utils.createProperty(part, "calendar", cal);
                part.ttip = Zapatec.Calendar.i18n("TIME_PART");
                part.navtype = 50;
                part._range = [];
                if (!range_end)
                    part._range = range_start;
                else {
                    part.rowSpan = 2;
                    for (var i = range_start; i <= range_end; ++i) {
                        var txt;
                        if (i < 10 && range_end >= 10)txt = '0' + i;
                        else txt = '' + i;
                        part._range[part._range.length] = txt;
                    }
                    var up = Zapatec.Utils.createElement("td", tr);
                    up.className = "up";
                    up.navtype = 201;
                    Zapatec.Utils.createProperty(up, "calendar", cal);
                    up.timePart = part;
                    if (Zapatec.is_khtml)
                        up.innerHTML = "&nbsp;";
                    Zapatec.Calendar._add_evs(up);
                    var down = Zapatec.Utils.createElement("td", tr2);
                    down.className = "down";
                    down.navtype = 202;
                    Zapatec.Utils.createProperty(down, "calendar", cal);
                    down.timePart = part;
                    if (Zapatec.is_khtml)
                        down.innerHTML = "&nbsp;";
                    Zapatec.Calendar._add_evs(down);
                }
                Zapatec.Calendar._add_evs(part);
                return part;
            }
            ;
            var hrs = cal.currentDate.getHours();
            var mins = cal.currentDate.getMinutes();
            if (cal.showsTime == "seconds") {
                var secs = cal.currentDate.getSeconds();
            }
            var t12 = !cal.time24;
            var pm = (hrs > 12);
            if (t12 && pm)hrs -= 12;
            var H = makeTimePart("hour", hrs, t12 ? 1 : 0, t12 ? 12 : 23);
            H._step = (cal.timeInterval > 30) ? (cal.timeInterval / 60) : 1;
            cell = Zapatec.Utils.createElement("td", row);
            cell.innerHTML = ":";
            cell.className = "colon";
            var M = makeTimePart("minute", mins, 0, 59);
            M._step = ((cal.timeInterval) && (cal.timeInterval < 60)) ? (cal.timeInterval) : 5;
            if (cal.showsTime == "seconds") {
                cell = Zapatec.Utils.createElement("td", row);
                cell.innerHTML = ":";
                cell.className = "colon";
                var S = makeTimePart("minute", secs, 0, 59);
                S._step = 5;
            }
            var AP = null;
            if (t12) {
                AP = makeTimePart("ampm", pm ? Zapatec.Calendar.i18n("pm", "ampm") : Zapatec.Calendar.i18n("am", "ampm"), [Zapatec.Calendar.i18n("am", "ampm"),Zapatec.Calendar.i18n("pm", "ampm")]);
                AP.className += " button";
            }
            else
                Zapatec.Utils.createElement("td", row).innerHTML = "&nbsp;";
            cal.onSetTime = function() {
                var hrs = this.currentDate.getHours();
                var mins = this.currentDate.getMinutes();
                if (this.showsTime == "seconds") {
                    var secs = cal.currentDate.getSeconds();
                }
                if (this.timeInterval) {
                    mins += this.timeInterval - ((mins - 1 + this.timeInterval) % this.timeInterval) - 1;
                }
                while (mins >= 60) {
                    mins -= 60;
                    ++hrs;
                }
                if (this.timeInterval > 60) {
                    var interval = this.timeInterval / 60;
                    if (hrs % interval != 0) {
                        hrs += interval - ((hrs - 1 + interval) % interval) - 1;
                    }
                    if (hrs >= 24) {
                        hrs -= 24;
                    }
                }
                var new_date = new Date(this.currentDate);
                if (this.getDateStatus && this.getDateStatus(this.currentDate, this.currentDate.getFullYear(), this.currentDate.getMonth(), this.currentDate.getDate(), hrs, mins)) {
                    hours = hrs;
                    minutes = mins;
                    do{
                        if (this.timeInterval) {
                            if (this.timeInterval < 60) {
                                minutes += this.timeInterval;
                            }
                            else {
                                hrs += this.timeInterval / 60;
                            }
                        }
                        else {
                            minutes += 5;
                        }
                        if (minutes >= 60) {
                            minutes -= 60;
                            hours += 1;
                        }
                        if (hours >= 24) {
                            hours -= 24;
                        }
                        new_date.setMinutes(minutes);
                        new_date.setHours(hours);
                        if (!this.getDateStatus(new_date, this.currentDate.getFullYear(), this.currentDate.getMonth(), this.currentDate.getDate(), hours, minutes)) {
                            hrs = hours;
                            mins = minutes;
                        }
                    }
                    while ((hrs != hours) || (mins != minutes));
                }
                this.currentDate.setMinutes(mins);
                this.currentDate.setHours(hrs);
                var pm = (hrs >= 12);
                if (pm && t12 && hrs != 12)hrs -= 12;
                if (!pm && t12 && hrs == 0)hrs = 12;
                H.firstChild.data = (hrs < 10) ? ("0" + hrs) : hrs;
                M.firstChild.data = (mins < 10) ? ("0" + mins) : mins;
                if (this.showsTime == "seconds") {
                    S.firstChild.data = (secs < 10) ? ("0" + secs) : secs;
                }
                if (t12)
                    AP.firstChild.data = pm ? Zapatec.Calendar.i18n("pm", "ampm") : Zapatec.Calendar.i18n("am", "ampm");
            };
            cal.onUpdateTime = function() {
                var date = this.currentDate;
                var h = parseInt(H.firstChild.data, 10);
                if (t12) {
                    if (/pm/i.test(AP.firstChild.data) && h < 12)
                        h += 12;
                    else if (/am/i.test(AP.firstChild.data) && h == 12)
                        h = 0;
                }
                var d = date.getDate();
                var m = date.getMonth();
                var y = date.getFullYear();
                date.setHours(h);
                date.setMinutes(parseInt(M.firstChild.data, 10));
                if (this.showsTime == "seconds") {
                    date.setSeconds(parseInt(S.firstChild.data, 10));
                }
                date.setFullYear(y);
                date.setMonth(m);
                date.setDate(d);
                this.dateClicked = false;
                this.callHandler();
            };
        })();
        if (this.monthsInRow != 1) {
            cell = Zapatec.Utils.createElement("td", row);
            cell.colSpan = ((this.weekNumbers) ? 8 : 7) * (this.monthsInRow - 1) - Math.ceil(emptyColspan);
            cell.className = "timetext";
            cell.innerHTML = "&nbsp";
        }
    }
    else {
        this.onSetTime = this.onUpdateTime = function() {
        };
    }
    row = Zapatec.Utils.createElement("tr", tfoot);
    row.className = "footrow";
    cell = hh(Zapatec.Calendar.i18n("SEL_DATE"), this.weekNumbers ? (8 * this.numberMonths) : (7 * this.numberMonths), 300);
    cell.className = "ttip";
    if (this.isPopup && !this.disableDrag) {
        cell.ttip = Zapatec.Calendar.i18n("DRAG_TO_MOVE");
        cell.style.cursor = "move";
    }
    this.tooltips = cell;
    div = this.monthsCombo = Zapatec.Utils.createElement("div", this.element);
    div.className = "combo";
    for (i = 0; i < 12; ++i) {
        var mn = Zapatec.Utils.createElement("div");
        mn.className = Zapatec.is_ie ? "label-IEfix" : "label";
        mn.month = i;
        mn.appendChild(window.document.createTextNode(Zapatec.Calendar.i18n(i, "smn")));
        div.appendChild(mn);
    }
    div = this.yearsCombo = Zapatec.Utils.createElement("div", this.element);
    div.className = "combo";
    for (i = 12; i > 0; --i) {
        var yr = Zapatec.Utils.createElement("div");
        yr.className = Zapatec.is_ie ? "label-IEfix" : "label";
        yr.appendChild(window.document.createTextNode("&nbsp;"));
        div.appendChild(yr);
    }
    div = this.histCombo = Zapatec.Utils.createElement("div", this.element);
    div.className = "combo history";
    this._init(this.firstDayOfWeek, this.date);
    parent.appendChild(this.element);
};
Zapatec.Calendar._keyEvent = function(ev) {
    if (!window.calendar) {
        return false;
    }
    (Zapatec.is_ie) && (ev = window.event);
    var cal = window.calendar;
    var act = (Zapatec.is_ie || ev.type == "keypress");
    var K = ev.keyCode;
    var date = new Date(cal.date);
    if (ev.ctrlKey) {
        switch (K) {case 37:act && Zapatec.Calendar.cellClick(cal._nav_pm);break;case 38:act && Zapatec.Calendar.cellClick(cal._nav_py);break;case 39:act && Zapatec.Calendar.cellClick(cal._nav_nm);break;case 40:act && Zapatec.Calendar.cellClick(cal._nav_ny);break;default:return false;}
    }
    else switch (K) {case 32:Zapatec.Calendar.cellClick(cal._nav_now);break;case 27:act && cal.callCloseHandler();break;case 37:if (act && !cal.multiple) {
        date.setTime(date.getTime() - 86400000);
        cal.setDate(date);
    }
        break;case 38:if (act && !cal.multiple) {
        date.setTime(date.getTime() - 7 * 86400000);
        cal.setDate(date);
    }
        break;case 39:if (act && !cal.multiple) {
        date.setTime(date.getTime() + 86400000);
        cal.setDate(date);
    }
        break;case 40:if (act && !cal.multiple) {
        date.setTime(date.getTime() + 7 * 86400000);
        cal.setDate(date);
    }
        break;case 13:if (act) {
        Zapatec.Calendar.cellClick(cal.currentDateEl);
    }
        break;default:return false;}
    return Zapatec.Utils.stopEvent(ev);
};
Zapatec.Calendar.prototype._init = function(firstDayOfWeek, date, last) {
    var
            today = new Date(),TD = today.getDate(),TY = today.getFullYear(),TM = today.getMonth();
    if (this.getDateStatus && !last) {
        var status = this.getDateStatus(date, date.getFullYear(), date.getMonth(), date.getDate());
        var backupDate = new Date(date);
        while (((status == true) || (status == "disabled")) && (backupDate.getMonth() == date.getMonth())) {
            date.setTime(date.getTime() + 86400000);
            var status = this.getDateStatus(date, date.getFullYear(), date.getMonth(), date.getDate());
        }
        if (backupDate.getMonth() != date.getMonth()) {
            date = new Date(backupDate);
            while (((status == true) || (status == "disabled")) && (backupDate.getMonth() == date.getMonth())) {
                date.setTime(date.getTime() - 86400000);
                var status = this.getDateStatus(date, date.getFullYear(), date.getMonth(), date.getDate());
            }
        }
        if (backupDate.getMonth() != date.getMonth()) {
            last = true;
            date = new Date(backupDate);
        }
    }
    var year = date.getFullYear();
    var month = date.getMonth();
    var rowsOfMonths = Math.floor(this.numberMonths / this.monthsInRow);
    var minMonth;
    var diffMonth,last_row,before_control;
    if (!this.vertical) {
        diffMonth = (this.controlMonth - 1);
        minMonth = month - diffMonth;
    }
    else {
        last_row = ((this.numberMonths - 1) % this.monthsInRow) + 1;
        before_control = (this.controlMonth - 1) % this.monthsInRow;
        bottom = (before_control >= (last_row) ? (last_row) : (before_control));
        diffMonth = (before_control) * (rowsOfMonths - 1) + Math.floor((this.controlMonth - 1) / this.monthsInRow) + bottom;
        minMonth = month - diffMonth;
    }
    var minYear = year;
    if (minMonth < 0) {
        minMonth += 12;
        --minYear;
    }
    var maxMonth = minMonth + this.numberMonths - 1;
    var maxYear = minYear;
    if (maxMonth > 11) {
        maxMonth -= 12;
        ++maxYear;
    }
    function disableControl(ctrl) {
        Zapatec.Calendar._del_evs(ctrl);
        ctrl.disabled = true;
        ctrl.className = "button";
        ctrl.innerHTML = "<div>&nbsp</div>";
    }
    function enableControl(ctrl, sign) {
        Zapatec.Calendar._add_evs(ctrl);
        ctrl.disabled = false;
        ctrl.className = "button nav";
        ctrl.innerHTML = sign;
    }
    if ((minYear <= this.minYear) || !this.yearNav) {
        if (!this._nav_py.disabled) {
            disableControl(this._nav_py);
        }
    }
    else {
        if (this._nav_py.disabled) {
            enableControl(this._nav_py, "&#x00ab;");
        }
    }
    if (maxYear >= this.maxYear || !this.yearNav) {
        if (!this._nav_ny.disabled) {
            disableControl(this._nav_ny);
        }
    }
    else {
        if (this._nav_ny.disabled) {
            enableControl(this._nav_ny, "&#x00bb;");
        }
    }
    if (((minYear == this.minYear) && (minMonth <= this.minMonth)) || (minYear < this.minYear)) {
        if (!this._nav_pm.disabled) {
            disableControl(this._nav_pm);
        }
    }
    else {
        if (this._nav_pm.disabled) {
            enableControl(this._nav_pm, "&#x2039;");
        }
    }
    if (((maxYear == this.maxYear) && (maxMonth >= this.maxMonth)) || (maxYear > this.maxYear)) {
        if (!this._nav_nm.disabled) {
            disableControl(this._nav_nm);
        }
    }
    else {
        if (this._nav_nm.disabled) {
            enableControl(this._nav_nm, "&#x203a;");
        }
    }
    upperMonth = this.maxMonth + 1;
    upperYear = this.maxYear;
    if (upperMonth > 11) {
        upperMonth -= 12;
        ++upperYear;
    }
    bottomMonth = this.minMonth - 1;
    bottomYear = this.minYear;
    if (bottomMonth < 0) {
        bottomMonth += 12;
        --bottomYear;
    }
    maxDate1 = new Date(maxYear, maxMonth, date.getMonthDays(maxMonth), 23, 59, 59, 999);
    maxDate2 = new Date(upperYear, upperMonth, 1, 0, 0, 0, 0);
    minDate1 = new Date(minYear, minMonth, 1, 0, 0, 0, 0);
    minDate2 = new Date(bottomYear, bottomMonth, date.getMonthDays(bottomMonth), 23, 59, 59, 999);
    if (maxDate1.getTime() > maxDate2.getTime()) {
        date.setTime(date.getTime() - (maxDate1.getTime() - maxDate2.getTime()));
    }
    if (minDate1.getTime() < minDate2.getTime()) {
        date.setTime(date.getTime() + (minDate2.getTime() - minDate1.getTime()) + 1);
    }
    delete maxDate1;
    delete maxDate2;
    delete minDate1;
    delete minDate2;
    this.firstDayOfWeek = firstDayOfWeek;
    if (!last) {
        this.currentDate = date;
    }
    this.date = date;
    (this.date = new Date(this.date)).setDateOnly(date);
    year = this.date.getFullYear();
    month = this.date.getMonth();
    var initMonth = date.getMonth();
    var mday = this.date.getDate();
    var no_days = date.getMonthDays();
    var months = new Array();
    if (this.numberMonths % this.monthsInRow > 0) {
        ++rowsOfMonths;
    }
    for (var l = 1; l <= rowsOfMonths; ++l) {
        months[l] = new Array();
        for (var k = 1; (k <= this.monthsInRow) && ((l - 1) * this.monthsInRow + k <= this.numberMonths); ++k) {
            var tmpDate = new Date(date);
            if (this.vertical) {
                var validMonth = date.getMonth() - diffMonth + ((k - 1) * (rowsOfMonths - 1) + (l - 1) + ((last_row < k) ? (last_row) : (k - 1)));
            }
            else {
                var validMonth = date.getMonth() - diffMonth + (l - 1) * this.monthsInRow + k - 1;
            }
            if (validMonth < 0) {
                tmpDate.setFullYear(tmpDate.getFullYear() - 1);
                validMonth = 12 + validMonth;
            }
            if (validMonth > 11) {
                tmpDate.setFullYear(tmpDate.getFullYear() + 1);
                validMonth = validMonth - 12;
            }
            tmpDate.setDate(1);
            tmpDate.setMonth(validMonth);
            var day1 = (tmpDate.getDay() - this.firstDayOfWeek) % 7;
            if (day1 < 0)
                day1 += 7;
            var hrs = tmpDate.getHours();
            tmpDate.setDate(-day1);
            tmpDate.setDate(tmpDate.getDate() + 1);
            if (hrs != tmpDate.getHours()) {
                tmpDate.setDate(1);
                tmpDate.setMonth(validMonth);
                tmpDate.setDate(-day1);
                tmpDate.setDate(tmpDate.getDate() + 1);
            }
            months[l][k] = tmpDate;
        }
    }
    var MN = Zapatec.Calendar.i18n(month, "smn");
    var weekend = Zapatec.Calendar.i18n("WEEKEND");
    var dates = this.multiple ? (this.datesCells = {}) : null;
    var DATETXT = this.getDateText;
    for (var l = 1; l <= rowsOfMonths; ++l) {
        var row = this.tbody[l].firstChild;
        for (var i = 7; --i > 0; row = row.nextSibling) {
            var cell = row.firstChild;
            var hasdays = false;
            for (var k = 1; (k <= this.monthsInRow) && ((l - 1) * this.monthsInRow + k <= this.numberMonths); ++k) {
                date = months[l][k];
                if (this.weekNumbers) {
                    cell.className = " day wn";
                    cell.innerHTML = date.getWeekNumber();
                    if (k > 1) {
                        Zapatec.Utils.addClass(cell, "month-left-border");
                    }
                    cell = cell.nextSibling;
                }
                row.className = "daysrow";
                var iday;
                for (j = 7; cell && (iday = date.getDate()) && (j > 0); date.setDate(iday + 1),((date.getDate() == iday) ? (date.setHours(1) && date.setDate(iday + 1)) : (false)),cell = cell.nextSibling,--j) {
                    var
                            wday = date.getDay(),dmonth = date.getMonth(),dyear = date.getFullYear();
                    cell.className = " day";
                    if ((!this.weekNumbers) && (j == 7) && (k != 1)) {
                        Zapatec.Utils.addClass(cell, "month-left-border");
                    }
                    if ((j == 1) && (k != this.monthsInRow)) {
                        Zapatec.Utils.addClass(cell, "month-right-border");
                    }
                    if (this.vertical) {
                        validMonth = initMonth - diffMonth + ((k - 1) * (rowsOfMonths - 1) + (l - 1) + ((last_row < k) ? (last_row) : (k - 1)));
                    }
                    else {
                        validMonth = initMonth - diffMonth + ((l - 1) * this.monthsInRow + k - 1);
                    }
                    if (validMonth < 0) {
                        validMonth = 12 + validMonth;
                    }
                    if (validMonth > 11) {
                        validMonth = validMonth - 12;
                    }
                    var current_month = !(cell.otherMonth = !(dmonth == validMonth));
                    if (!current_month) {
                        if (this.showsOtherMonths)
                            cell.className += " othermonth";
                        else {
                            cell.className += " true";
                            cell.innerHTML = "<div>&nbsp;</div>";
                            continue;
                        }
                    }
                    else
                        hasdays = true;
                    cell.innerHTML = DATETXT ? DATETXT(date, dyear, dmonth, iday) : iday;
                    dates && (dates[date.print("%Y%m%d")] = cell);
                    if (this.getDateStatus) {
                        var status = this.getDateStatus(date, dyear, dmonth, iday);
                        if (this.getDateToolTip) {
                            var toolTip = this.getDateToolTip(date, dyear, dmonth, iday);
                            if (toolTip)
                                cell.title = toolTip;
                        }
                        if (status == true) {
                            cell.className += " disabled";
                        }
                        else {
                            cell.className += " " + status;
                        }
                    }
                    if (!cell.disabled) {
                        cell.caldate = [dyear,dmonth,iday];
                        cell.ttip = "_";
                        if (!this.multiple && current_month && iday == this.currentDate.getDate() && this.hiliteToday && (dmonth == this.currentDate.getMonth()) && (dyear == this.currentDate.getFullYear())) {
                            cell.className += " selected";
                            this.currentDateEl = cell;
                        }
                        if (dyear == TY && dmonth == TM && iday == TD) {
                            cell.className += " today";
                            cell.ttip += Zapatec.Calendar.i18n("PART_TODAY");
                        }
                        if ((weekend != null) && (weekend.indexOf(wday.toString()) != -1)) {
                            cell.className += cell.otherMonth ? " oweekend" : " weekend";
                        }
                    }
                }
                if (!(hasdays || this.showsOtherMonths))
                    row.className = "emptyrow";
            }
            if ((i == 1) && (l < rowsOfMonths)) {
                if (row.className == "emptyrow") {
                    row = row.previousSibling;
                }
                cell = row.firstChild;
                while (cell != null) {
                    Zapatec.Utils.addClass(cell, "month-bottom-border");
                    cell = cell.nextSibling;
                }
            }
        }
    }
    if (this.numberMonths == 1) {
        this.title.innerHTML = Zapatec.Calendar.i18n(month, "mn") + ", " + year;
        if (this.params && this.params.titleHtml)
            if (typeof this.params.titleHtml == 'function')
                this.title.innerHTML = this.params.titleHtml(this.title.innerHTML, month, year)
            else
                this.title.innerHTML += this.params.titleHtml
    }
    else {
        if (this.params && this.params.titleHtml)
            if (typeof this.params.titleHtml == 'function')
                this.title.innerHTML = this.params.titleHtml(Zapatec.Calendar.i18n(month, "mn") + ", " + year, month, year)
            else
                this.title.innerHTML = this.params.titleHtml
        for (var l = 1; l <= rowsOfMonths; ++l) {
            for (var k = 1; (k <= this.monthsInRow) && ((l - 1) * this.monthsInRow + k <= this.numberMonths); ++k) {
                if (this.vertical) {
                    validMonth = month - diffMonth + ((k - 1) * (rowsOfMonths - 1) + (l - 1) + ((last_row < k) ? (last_row) : (k - 1)));
                }
                else {
                    validMonth = month - diffMonth + (l - 1) * this.monthsInRow + k - 1;
                }
                validYear = year;
                if (validMonth < 0) {
                    --validYear;
                    validMonth = 12 + validMonth;
                }
                if (validMonth > 11) {
                    ++validYear;
                    validMonth = validMonth - 12;
                }
                this.titles[l][k].innerHTML = Zapatec.Calendar.i18n(validMonth, "mn") + ", " + validYear;
            }
        }
    }
    this.onSetTime();
    this._initMultipleDates();
    this.updateWCH();
};
Zapatec.Calendar.prototype._initMultipleDates = function() {
    if (this.multiple) {
        for (var i in this.multiple) {
            var cell = this.datesCells[i];
            var d = this.multiple[i];
            if (!d)
                continue;
            if (cell)
                cell.className += " selected";
        }
    }
};
Zapatec.Calendar.prototype._toggleMultipleDate = function(date) {
    if (this.multiple) {
        var ds = date.print("%Y%m%d");
        var cell = this.datesCells[ds];
        if (cell) {
            var d = this.multiple[ds];
            if (!d) {
                Zapatec.Utils.addClass(cell, "selected");
                this.multiple[ds] = date;
            }
            else {
                Zapatec.Utils.removeClass(cell, "selected");
                delete this.multiple[ds];
            }
        }
    }
};
Zapatec.Calendar.prototype.setDateToolTipHandler = function(unaryFunction) {
    this.getDateToolTip = unaryFunction;
};
Zapatec.Calendar.prototype.setDate = function(date, justInit) {
    if (!date)
        date = new Date();
    if (!date.equalsTo(this.date)) {
        var year = date.getFullYear(),m = date.getMonth();
        if (year < this.minYear || (year == this.minYear && m < this.minMonth))
            this.showHint("<div class='error'>" + Zapatec.Calendar.i18n("E_RANGE") + " »»»</div>");
        else if (year > this.maxYear || (year == this.maxYear && m > this.maxMonth))
            this.showHint("<div class='error'>««« " + Zapatec.Calendar.i18n("E_RANGE") + "</div>");
        this._init(this.firstDayOfWeek, date, justInit);
    }
};
Zapatec.Calendar.prototype.showHint = function(text) {
    this.tooltips.innerHTML = text;
};
Zapatec.Calendar.prototype.reinit = function() {
    this._init(this.firstDayOfWeek, this.date);
};
Zapatec.Calendar.prototype.refresh = function() {
    var p = this.isPopup ? null : this.element.parentNode;
    var x = parseInt(this.element.style.left);
    var y = parseInt(this.element.style.top);
    this.destroy();
    this.dateStr = this.date;
    this.create(p);
    if (this.isPopup)
        this.showAt(x, y);
    else
        this.show();
};
Zapatec.Calendar.prototype.setFirstDayOfWeek = function(firstDayOfWeek) {
    if (this.firstDayOfWeek != firstDayOfWeek) {
        this._init(firstDayOfWeek, this.date);
        var rowsOfMonths = Math.floor(this.numberMonths / this.monthsInRow);
        if (this.numberMonths % this.monthsInRow > 0) {
            ++rowsOfMonths;
        }
        for (var l = 1; l <= rowsOfMonths; ++l) {
            this.firstdayname = this.rowsOfDayNames[l];
            this._displayWeekdays();
        }
    }
};
Zapatec.Calendar.prototype.setDateStatusHandler = Zapatec.Calendar.prototype.setDisabledHandler = function(unaryFunction) {
    this.getDateStatus = unaryFunction;
};
Zapatec.Calendar.prototype.setRange = function(A, Z) {
    var m,a = Math.min(A, Z),z = Math.max(A, Z);
    this.minYear = m = Math.floor(a);
    this.minMonth = (m == a) ? 0 : Math.ceil((a - m) * 100 - 1);
    this.maxYear = m = Math.floor(z);
    this.maxMonth = (m == z) ? 11 : Math.ceil((z - m) * 100 - 1);
};
Zapatec.Calendar.prototype.setMultipleDates = function(multiple) {
    if (!multiple || typeof multiple == "undefined")return;
    this.multiple = {};
    for (var i = multiple.length; --i >= 0;) {
        var d = multiple[i];
        var ds = d.print("%Y%m%d");
        this.multiple[ds] = d;
    }
};
Zapatec.Calendar.prototype.submitFlatDates = function()
{
    if (typeof this.params.flatCallback == "function") {
        Zapatec.Utils.sortOrder = (this.sortOrder != "asc" && this.sortOrder != "desc" && this.sortOrder != "none") ? "none" : this.sortOrder;
        if (this.multiple && (Zapatec.Utils.sortOrder != "none")) {
            var dateArray = new Array();
            for (var i in this.multiple) {
                var currentDate = this.multiple[i];
                if (currentDate) {
                    dateArray[dateArray.length] = currentDate;
                }
                dateArray.sort(Zapatec.Utils.compareDates);
            }
            this.multiple = {};
            for (var i = 0; i < dateArray.length; i++) {
                var d = dateArray[i];
                var ds = d.print("%Y%m%d");
                this.multiple[ds] = d;
            }
        }
        this.params.flatCallback(this);
    }
}
Zapatec.Calendar.prototype.callHandler = function() {
    if (this.onSelected) {
        this.onSelected(this, this.date.print(this.dateFormat));
    }
};
Zapatec.Calendar.prototype.updateHistory = function() {
    var a,i,d,tmp,s,str = "",len = Zapatec.Calendar.prefs.hsize - 1;
    if (Zapatec.Calendar.prefs.history) {
        a = Zapatec.Calendar.prefs.history.split(/,/);
        i = 0;
        while (i < len && (tmp = a[i++])) {
            s = tmp.split(/\//);
            d = new Date(parseInt(s[0], 10), parseInt(s[1], 10) - 1, parseInt(s[2], 10), parseInt(s[3], 10), parseInt(s[4], 10));
            if (!d.dateEqualsTo(this.date))
                str += "," + tmp;
        }
    }
    Zapatec.Calendar.prefs.history = this.date.print("%Y/%m/%d/%H/%M") + str;
    Zapatec.Calendar.savePrefs();
};
Zapatec.Calendar.prototype.callCloseHandler = function() {
    if (this.dateClicked) {
        this.updateHistory();
    }
    if (this.onClose) {
        this.onClose(this);
    }
    this.hideShowCovered();
};
Zapatec.Calendar.prototype.destroy = function() {
    this.hide();
    Zapatec.Utils.destroy(this.element);
    Zapatec.Utils.destroy(this.WCH);
    Zapatec.Calendar._C = null;
    window.calendar = null;
};
Zapatec.Calendar.prototype.reparent = function(new_parent) {
    var el = this.element;
    el.parentNode.removeChild(el);
    new_parent.appendChild(el);
};
Zapatec.Calendar._checkCalendar = function(ev) {
    if (!window.calendar) {
        return false;
    }
    var el = Zapatec.is_ie ? Zapatec.Utils.getElement(ev) : Zapatec.Utils.getTargetElement(ev);
    for (; el != null && el != calendar.element; el = el.parentNode);
    if (el == null) {
        window.calendar.callCloseHandler();
    }
};
Zapatec.Calendar.prototype.updateWCH = function(other_el) {
    Zapatec.Utils.setupWCH_el(this.WCH, this.element, other_el);
};
Zapatec.Calendar.prototype.show = function() {
    var rows = this.table.getElementsByTagName("tr");
    for (var i = rows.length; i > 0;) {
        var row = rows[--i];
        Zapatec.Utils.removeClass(row, "rowhilite");
        var cells = row.getElementsByTagName("td");
        for (var j = cells.length; j > 0;) {
            var cell = cells[--j];
            Zapatec.Utils.removeClass(cell, "hilite");
            Zapatec.Utils.removeClass(cell, "active");
        }
    }
    if (this.element.style.display != "block") {
        this.element.style.display = "block";
    }
    this.hidden = false;
    if (this.isPopup) {
        this.updateWCH();
        window.calendar = this;
        if (!this.noGrab) {
            Zapatec.Utils.addEvent(window.document, "keydown", Zapatec.Calendar._keyEvent);
            Zapatec.Utils.addEvent(window.document, "keypress", Zapatec.Calendar._keyEvent);
            Zapatec.Utils.addEvent(window.document, "mousedown", Zapatec.Calendar._checkCalendar);
        }
    }
    this.hideShowCovered();
};
Zapatec.Calendar.prototype.hide = function() {
    if (this.isPopup) {
        Zapatec.Utils.removeEvent(window.document, "keydown", Zapatec.Calendar._keyEvent);
        Zapatec.Utils.removeEvent(window.document, "keypress", Zapatec.Calendar._keyEvent);
        Zapatec.Utils.removeEvent(window.document, "mousedown", Zapatec.Calendar._checkCalendar);
    }
    this.element.style.display = "none";
    Zapatec.Utils.hideWCH(this.WCH);
    this.hidden = true;
    this.hideShowCovered();
};
Zapatec.Calendar.prototype.showAt = function(x, y) {
    var s = this.element.style;
    s.left = x + "px";
    s.top = y + "px";
    this.show();
};
Zapatec.Calendar.prototype.showAtElement = function(el, opts) {
    var self = this;
    var p = Zapatec.Utils.getElementOffset(el);
    if (!opts || typeof opts != "string") {
        this.showAt(p.x, p.y + el.offsetHeight);
        return true;
    }
    this.element.style.display = "block";
    var w = self.element.offsetWidth;
    var h = self.element.offsetHeight;
    self.element.style.display = "none";
    var valign = opts.substr(0, 1);
    var halign = "l";
    if (opts.length > 1) {
        halign = opts.substr(1, 1);
    }
    switch (valign) {case"T":p.y -= h;break;case"B":p.y += el.offsetHeight;break;case"C":p.y += (el.offsetHeight - h) / 2;break;case"t":p.y += el.offsetHeight - h;break;case"b":break;}
    switch (halign) {case"L":p.x -= w;break;case"R":p.x += el.offsetWidth;break;case"C":p.x += (el.offsetWidth - w) / 2;break;case"l":p.x += el.offsetWidth - w;break;case"r":break;}
    p.width = w;
    p.height = h;
    self.monthsCombo.style.display = "none";
    Zapatec.Utils.fixBoxPosition(p, 10);
    self.showAt(p.x, p.y);
};
Zapatec.Calendar.prototype.setDateFormat = function(str) {
    this.dateFormat = str;
};
Zapatec.Calendar.prototype.setTtDateFormat = function(str) {
    this.ttDateFormat = str;
};
Zapatec.Calendar.prototype.parseDate = function(str, fmt) {
    if (!str)
        return this.setDate(this.date);
    if (!fmt)
        fmt = this.dateFormat;
    var date = Date.parseDate(str, fmt);
    return this.setDate(date);
};
Zapatec.Calendar.prototype.hideShowCovered = function() {
    if (!Zapatec.is_ie5)
        return;
    var self = this;
    function getVisib(obj) {
        var value = obj.style.visibility;
        if (!value) {
            if (window.document.defaultView && typeof(window.document.defaultView.getComputedStyle) == "function") {
                if (!Zapatec.is_khtml)
                    value = window.document.defaultView.getComputedStyle(obj, "").getPropertyValue("visibility");
                else
                    value = '';
            }
            else if (obj.currentStyle) {
                value = obj.currentStyle.visibility;
            }
            else
                value = '';
        }
        return value;
    }
    ;
    var tags = ["applet","iframe","select"];
    var el = self.element;
    var p = Zapatec.Utils.getAbsolutePos(el);
    var EX1 = p.x;
    var EX2 = el.offsetWidth + EX1;
    var EY1 = p.y;
    var EY2 = el.offsetHeight + EY1;
    for (var k = tags.length; k > 0;) {
        var ar = window.document.getElementsByTagName(tags[--k]);
        var cc = null;
        for (var i = ar.length; i > 0;) {
            cc = ar[--i];
            p = Zapatec.Utils.getAbsolutePos(cc);
            var CX1 = p.x;
            var CX2 = cc.offsetWidth + CX1;
            var CY1 = p.y;
            var CY2 = cc.offsetHeight + CY1;
            if (self.hidden || (CX1 > EX2) || (CX2 < EX1) || (CY1 > EY2) || (CY2 < EY1)) {
                if (!cc.__msh_save_visibility) {
                    cc.__msh_save_visibility = getVisib(cc);
                }
                cc.style.visibility = cc.__msh_save_visibility;
            }
            else {
                if (!cc.__msh_save_visibility) {
                    cc.__msh_save_visibility = getVisib(cc);
                }
                cc.style.visibility = "hidden";
            }
        }
    }
};
Zapatec.Calendar.prototype._displayWeekdays = function() {
    var fdow = this.firstDayOfWeek;
    var cell = this.firstdayname;
    var weekend = Zapatec.Calendar.i18n("WEEKEND");
    for (k = 1; (k <= this.monthsInRow) && (cell); ++k) {
        for (var i = 0; i < 7; ++i) {
            cell.className = " day name";
            if ((!this.weekNumbers) && (i == 0) && (k != 1)) {
                Zapatec.Utils.addClass(cell, "month-left-border");
            }
            if ((i == 6) && (k != this.monthsInRow)) {
                Zapatec.Utils.addClass(cell, "month-right-border");
            }
            var realday = (i + fdow) % 7;
            if ((!this.disableFdowClick) && ((this.params && this.params.fdowClick) || i)) {
                if (Zapatec.Calendar.i18n("DAY_FIRST") != null) {
                    cell.ttip = Zapatec.Calendar.i18n("DAY_FIRST").replace("%s", Zapatec.Calendar.i18n(realday, "dn"));
                }
                cell.navtype = 100;
                cell.calendar = this;
                cell.fdow = realday;
                Zapatec.Calendar._add_evs(cell);
            }
            if ((weekend != null) && (weekend.indexOf(realday.toString()) != -1)) {
                Zapatec.Utils.addClass(cell, "weekend");
            }
            cell.innerHTML = Zapatec.Calendar.i18n((i + fdow) % 7, "sdn");
            cell = cell.nextSibling;
        }
        if (this.weekNumbers && cell) {
            cell = cell.nextSibling;
        }
    }
};
Zapatec.Utils.compareDates = function(date1, date2)
{
    if (Zapatec.Calendar.prefs.sortOrder == "asc")
        return date1 - date2;
    else
        return date2 - date1;
}
Zapatec.Calendar.prototype._hideCombos = function() {
    this.monthsCombo.style.display = "none";
    this.yearsCombo.style.display = "none";
    this.histCombo.style.display = "none";
    this.updateWCH();
};
Zapatec.Calendar.prototype._dragStart = function(ev) {
    ev || (ev = window.event);
    if (this.dragging) {
        return;
    }
    this.dragging = true;
    var posX = ev.clientX + window.document.body.scrollLeft;
    var posY = ev.clientY + window.document.body.scrollTop;
    var st = this.element.style;
    this.xOffs = posX - parseInt(st.left);
    this.yOffs = posY - parseInt(st.top);
    Zapatec.Utils.addEvent(window.document, "mousemove", Zapatec.Calendar.calDragIt);
    Zapatec.Utils.addEvent(window.document, "mouseover", Zapatec.Calendar.calDragIt);
    Zapatec.Utils.addEvent(window.document, "mouseup", Zapatec.Calendar.calDragEnd);
};
Date._MD = [31,28,31,30,31,30,31,31,30,31,30,31];
Date.SECOND = 1000;
Date.MINUTE = 60 * Date.SECOND;
Date.HOUR = 60 * Date.MINUTE;
Date.DAY = 24 * Date.HOUR;
Date.WEEK = 7 * Date.DAY;
Date.prototype.getMonthDays = function(month) {
    var year = this.getFullYear();
    if (typeof month == "undefined") {
        month = this.getMonth();
    }
    if (((0 == (year % 4)) && ((0 != (year % 100)) || (0 == (year % 400)))) && month == 1) {
        return 29;
    }
    else {
        return Date._MD[month];
    }
};
Date.prototype.getDayOfYear = function() {
    var now = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
    var then = new Date(this.getFullYear(), 0, 0, 0, 0, 0);
    var time = now - then;
    return Math.round(time / Date.DAY);
};
Date.prototype.getWeekNumber = function() {
    var d = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
    var DoW = d.getDay();
    d.setDate(d.getDate() - (DoW + 6) % 7 + 3);
    var ms = d.valueOf();
    d.setMonth(0);
    d.setDate(4);
    return Math.round((ms - d.valueOf()) / (7 * 864e5)) + 1;
};
Date.prototype.equalsTo = function(date) {
    return((this.getFullYear() == date.getFullYear()) && (this.getMonth() == date.getMonth()) && (this.getDate() == date.getDate()) && (this.getHours() == date.getHours()) && (this.getMinutes() == date.getMinutes()));
};
Date.prototype.dateEqualsTo = function(date) {
    return((this.getFullYear() == date.getFullYear()) && (this.getMonth() == date.getMonth()) && (this.getDate() == date.getDate()));
};
Date.prototype.setDateOnly = function(date) {
    var tmp = new Date(date);
    this.setDate(1);
    this.setFullYear(tmp.getFullYear());
    this.setMonth(tmp.getMonth());
    this.setDate(tmp.getDate());
};
Date.prototype.print = function(str) {
    var m = this.getMonth();
    var d = this.getDate();
    var y = this.getFullYear();
    var wn = this.getWeekNumber();
    var w = this.getDay();
    var s = {};
    var hr = this.getHours();
    var pm = (hr >= 12);
    var ir = (pm) ? (hr - 12) : hr;
    var dy = this.getDayOfYear();
    if (ir == 0)
        ir = 12;
    var min = this.getMinutes();
    var sec = this.getSeconds();
    s["%a"] = Zapatec.Calendar.i18n(w, "sdn");
    s["%A"] = Zapatec.Calendar.i18n(w, "dn");
    s["%b"] = Zapatec.Calendar.i18n(m, "smn");
    s["%B"] = Zapatec.Calendar.i18n(m, "mn");
    s["%C"] = 1 + Math.floor(y / 100);
    s["%d"] = (d < 10) ? ("0" + d) : d;
    s["%e"] = d;
    s["%H"] = (hr < 10) ? ("0" + hr) : hr;
    s["%I"] = (ir < 10) ? ("0" + ir) : ir;
    s["%j"] = (dy < 100) ? ((dy < 10) ? ("00" + dy) : ("0" + dy)) : dy;
    s["%k"] = hr ? hr : "0";
    s["%l"] = ir;
    s["%m"] = (m < 9) ? ("0" + (1 + m)) : (1 + m);
    s["%M"] = (min < 10) ? ("0" + min) : min;
    s["%n"] = "\n";
    s["%p"] = pm ? "PM" : "AM";
    s["%P"] = pm ? "pm" : "am";
    s["%s"] = Math.floor(this.getTime() / 1000);
    s["%S"] = (sec < 10) ? ("0" + sec) : sec;
    s["%t"] = "\t";
    s["%U"] = s["%W"] = s["%V"] = (wn < 10) ? ("0" + wn) : wn;
    s["%u"] = (w == 0) ? 7 : w;
    s["%w"] = w ? w : "0";
    s["%y"] = '' + y % 100;
    if (s["%y"] < 10) {
        s["%y"] = "0" + s["%y"];
    }
    s["%Y"] = y;
    s["%%"] = "%";
    var re = /%./g;
    var a = str.match(re) || [];
    for (var i = 0; i < a.length; i++) {
        var tmp = s[a[i]];
        if (tmp) {
            re = new RegExp(a[i], 'g');
            str = str.replace(re, tmp);
        }
    }
    return str;
};
Date.parseDate = function(str, format) {
    var fmt = format,strPointer = 0,token = null,parseFunc = null,valueLength = null,valueRange = null,valueType = null,date = new Date(),values = {};
    var numberRules = ["%d","%H","%I","%m","%M","%S","%s","%W","%u","%w","%y","%e","%k","%l","%s","%Y","%C"];
    function isNumberRule(rule) {
        if (Zapatec.Utils.arrIndexOf(numberRules, rule) != -1) {
            return true;
        }
        return false;
    }
    function parseString() {
        for (var iString = valueRange[0]; iString < valueRange[1]; ++iString) {
            var value = Zapatec.Calendar.i18n(iString, valueType);
            if (!value) {
                return null;
            }
            if (value == str.substr(strPointer, value.length)) {
                valueLength = value.length;
                return iString;
            }
        }
        return null;
    }
    function parseNumber() {
        var val = str.substr(strPointer, valueLength);
        if (val.length != valueLength || /$\d+^/.test(val)) {
            return null;
        }
        return parseInt(val, 10);
    }
    function parseAMPM() {
        var result = (str.substr(strPointer, valueLength).toLowerCase() == Zapatec.Calendar.i18n("pm", "ampm")) ? true : false;
        return result || ((str.substr(strPointer, valueLength).toLowerCase() == Zapatec.Calendar.i18n("am", "ampm")) ? false : null);
    }
    function parseCharacter() {
        return"";
    }
    function parseRule(rule) {
        return(values[rule] = parseFunc());
    }
    function wasParsed(rule) {
        if (typeof rule == "undefined" || rule === null) {
            return false;
        }
        return true;
    }
    function getValue() {
        for (var i = 0; i < arguments.length; ++i) {
            if (arguments[i] !== null && typeof arguments[i] != "undefined" && !isNaN(arguments[i])) {
                return arguments[i];
            }
        }
        return null;
    }
    if (typeof fmt != "string" || typeof str != "string" || str == "" || fmt == "") {
        return null;
    }
    while (fmt) {
        parseFunc = parseNumber;
        valueLength = fmt.indexOf("%");
        valueLength = (valueLength == -1) ? fmt.length : valueLength;
        token = fmt.slice(0, valueLength);
        if (token != str.substr(strPointer, valueLength)) {
            return null;
        }
        strPointer += valueLength;
        fmt = fmt.slice(valueLength);
        if (fmt == "") {
            break;
        }
        token = fmt.slice(0, 2);
        valueLength = 2;
        switch (token) {case"%A":case"%a":{
            valueType = (token == "%A") ? "dn" : "sdn";
            valueRange = [0,7];
            parseFunc = parseString;
            break;
        }
            case"%B":case"%b":{
            valueType = (token == "%B") ? "mn" : "smn";
            valueRange = [0,12];
            parseFunc = parseString;
            break;
        }
            case"%p":case"%P":{
            parseFunc = parseAMPM;
            break;
        }
            case"%Y":{
                valueLength = 4;
                if (isNumberRule(fmt.substr(2, 2))) {
                    return null;
                }
                while (isNaN(parseInt(str.charAt(strPointer + valueLength - 1))) && valueLength > 0) {
                    --valueLength;
                }
                if (valueLength == 0) {
                    break;
                }
                break;
            }
            case"%C":case"%s":{
            valueLength = 1;
            if (isNumberRule(fmt.substr(2, 2))) {
                return null;
            }
            while (!isNaN(parseInt(str.charAt(strPointer + valueLength)))) {
                ++valueLength;
            }
            break;
        }
            case"%k":case"%l":case"%e":{
            valueLength = 1;
            if (isNumberRule(fmt.substr(2, 2))) {
                return null;
            }
            if (!isNaN(parseInt(str.charAt(strPointer + 1)))) {
                ++valueLength;
            }
            break;
        }
            case"%j":valueLength = 3;break;case"%u":case"%w":valueLength = 1;case"%y":case"%m":case"%d":case"%W":case"%H":case"%I":case"%M":case"%S":{
            break;
        }}
        if (parseRule(token) === null) {
            return null;
        }
        strPointer += valueLength;
        fmt = fmt.slice(2);
    }
    if (wasParsed(values["%s"])) {
        date.setTime(values["%s"] * 1000);
    }
    else {
        var year = getValue(values["%Y"], values["%y"] + --values["%C"] * 100, values["%y"] + (date.getFullYear() - date.getFullYear() % 100), values["%C"] * 100 + date.getFullYear() % 100);
        var month = getValue(values["%m"] - 1, values["%b"], values["%B"]);
        var day = getValue(values["%d"] || values["%e"]);
        if (day === null || month === null) {
            var dayOfWeek = getValue(values["%a"], values["%A"], values["%u"] == 7 ? 0 : values["%u"], values["%w"]);
        }
        var hour = getValue(values["%H"], values["%k"]);
        if (hour === null && (wasParsed(values["%p"]) || wasParsed(values["%P"]))) {
            var pm = getValue(values["%p"], values["%P"]);
            hour = getValue(values["%I"], values["%l"]);
            hour = pm ? ((hour == 12) ? 12 : (hour + 12)) : ((hour == 12) ? (0) : hour);
        }
        if (year || year === 0) {
            date.setFullYear(year);
        }
        if (month || month === 0) {
            date.setMonth(month);
        }
        if (day || day === 0) {
            date.setDate(day);
        }
        if (wasParsed(values["%j"])) {
            date.setMonth(0);
            date.setDate(1);
            date.setDate(values["%j"]);
        }
        if (wasParsed(dayOfWeek)) {
            date.setDate(date.getDate() + (dayOfWeek - date.getDay()));
        }
        if (wasParsed(values["%W"])) {
            var weekNumber = date.getWeekNumber();
            if (weekNumber != values["%W"]) {
                date.setDate(date.getDate() + (values["%W"] - weekNumber) * 7);
            }
        }
        if (hour !== null) {
            date.setHours(hour);
        }
        if (wasParsed(values["%M"])) {
            date.setMinutes(values["%M"]);
        }
        if (wasParsed(values["%S"])) {
            date.setSeconds(values["%S"]);
        }
    }
    if (date.print(format) != str) {
        return null;
    }
    return date;
};
Date.prototype.__msh_oldSetFullYear = Date.prototype.setFullYear;
Date.prototype.setFullYear = function(y) {
    var d = new Date(this);
    d.__msh_oldSetFullYear(y);
    if (d.getMonth() != this.getMonth())
        this.setDate(28);
    this.__msh_oldSetFullYear(y);
};
Date.prototype.compareDatesOnly = function(date1, date2) {
    var year1 = date1.getYear();
    var year2 = date2.getYear();
    var month1 = date1.getMonth();
    var month2 = date2.getMonth();
    var day1 = date1.getDate();
    var day2 = date2.getDate();
    if (year1 > year2) {
        return-1;
    }
    if (year2 > year1) {
        return 1;
    }
    if (month1 > month2) {
        return-1;
    }
    if (month2 > month1) {
        return 1;
    }
    if (day1 > day2) {
        return-1;
    }
    if (day2 > day1) {
        return 1;
    }
    return 0;
}
Zapatec.Setup = function() {
};
Zapatec.Setup.test = true;
Zapatec.Calendar.setup = function(params) {
    paramsList = ["id"];
    function param_default(pname, def) {
        if (typeof params[pname] == "undefined") {
            params[pname] = def;
        }
        paramsList.push(pname);
    }
    ;
    params.id = Zapatec.Utils.generateID("calendar");
    param_default("inputField", null);
    param_default("displayArea", null);
    param_default("button", null);
    param_default("eventName", "click");
    param_default("closeEventName", null);
    param_default("ifFormat", Zapatec.Calendar.i18n("DEF_DATE_FORMAT"));
    param_default("daFormat", "%Y/%m/%d");
    param_default("singleClick", true);
    param_default("disableFunc", null);
    param_default("dateStatusFunc", params["disableFunc"]);
    param_default("dateText", null);
    param_default("firstDay", null);
    param_default("align", "Br");
    param_default("range", [1900,2999]);
    param_default("weekNumbers", true);
    param_default("flat", null);
    param_default("flatCallback", null);
    param_default("onSelect", null);
    param_default("onClose", null);
    param_default("onUpdate", null);
    param_default("date", null);
    param_default("showsTime", false);
    param_default("sortOrder", "asc");
    param_default("timeFormat", "24");
    param_default("timeInterval", null);
    param_default("electric", true);
    param_default("step", 2);
    param_default("position", null);
    param_default("cache", false);
    param_default("showOthers", false);
    param_default("multiple", null);
    param_default("saveDate", null);
    param_default("fdowClick", false);
    param_default("titleHtml", null);
    param_default("noHelp", false);
    param_default("noCloseButton", false);
    param_default("disableYearNav", false);
    param_default("disableFdowChange", false);
    if (params.weekNumbers) {
        params.disableFdowChange = true;
        params.firstDay = 1;
    }
    param_default("disableDrag", false);
    param_default("numberMonths", 1);
    if ((params.numberMonths > 12) || (params.numberMonths < 1)) {
        params.numberMonths = 1;
    }
    if (params.numberMonths > 1) {
        params.showOthers = false;
    }
    params.numberMonths = parseInt(params.numberMonths, 10);
    param_default("controlMonth", 1);
    if ((params.controlMonth > params.numberMonths) || (params.controlMonth < 1)) {
        params.controlMonth = 1;
    }
    params.controlMonth = parseInt(params.controlMonth, 10);
    param_default("vertical", false);
    if (params.monthsInRow > params.numberMonths) {
        params.monthsInRow = params.numberMonths;
    }
    param_default("monthsInRow", params.numberMonths);
    params.monthsInRow = parseInt(params.monthsInRow, 10);
    param_default("multiple", false);
    if (params.multiple) {
        params.singleClick = false;
    }
    param_default("canType", false);
    var tmp = ["inputField","displayArea","button"];
    for (var i in tmp) {
        if (typeof params[tmp[i]] == "string") {
            params[tmp[i]] = document.getElementById(params[tmp[i]]);
        }
    }
    if (!params.inputField) {
        params.canType = false;
    }
    else {
        params.inputField.setAttribute("autocomplete", "off");
    }
    if (!(params.flat || params.multiple || params.inputField || params.displayArea || params.button)) {
        alert("Calendar.setup '" + params.id + "':\n  Nothing to setup (no fields found).  Please check your code");
        return false;
    }
    if (((params.timeInterval) && ((params.timeInterval !== Math.floor(params.timeInterval)) || ((60 % params.timeInterval !== 0) && (params.timeInterval % 60 !== 0)))) || (params.timeInterval > 360)) {
        alert("'" + params.id + "': timeInterval option can only have the following number of minutes:\n1, 2, 3, 4, 5, 6, 10, 15, 30,  60, 120, 180, 240, 300, 360 ");
        params.timeInterval = null;
    }
    if (params.date && !Date.parse(params.date)) {
        alert("'" + params.id + "' Start Date Invalid: " + params.date + ".\nSee date option.\nDefaulting to today.");
        params.date = null;
    }
    if (params.saveDate) {
        param_default("cookiePrefix", window.location.href + "--" + params.button.id);
        var cookieName = params.cookiePrefix;
        var newdate = Zapatec.Utils.getCookie(cookieName);
        if (newdate != null) {
            document.getElementById(params.inputField.id).value = newdate;
        }
    }
    for (var ii in params) {
        if (typeof params.constructor.prototype[ii] != "undefined") {
            continue;
        }
        if (Zapatec.Utils.arrIndexOf(paramsList, ii) == -1) {
            alert("Wrong config option: " + ii);
        }
    }
    function onSelect(cal) {
        var p = cal.params;
        var update = (cal.dateClicked || p.electric);
        if (update && p.flat) {
            if (typeof p.flatCallback == "function")
            {
                if (!p.multiple)
                    p.flatCallback(cal);
            }
            else
                alert("'" + cal.id + "': No flatCallback given -- doing nothing.");
            return false;
        }
        if (update && p.inputField) {
            p.inputField.value = cal.currentDate.print(p.ifFormat);
            if (typeof p.inputField.onchange == "function")
                p.inputField.onchange();
        }
        if (update && p.displayArea)
            p.displayArea.innerHTML = cal.currentDate.print(p.daFormat);
        if (update && p.singleClick && cal.dateClicked)
            cal.callCloseHandler();
        if (update && typeof p.onUpdate == "function")
            p.onUpdate(cal);
        if (p.saveDate) {
            var cookieName = p.cookiePrefix;
            Zapatec.Utils.writeCookie(cookieName, p.inputField.value, null, '/', p.saveDate);
        }
    }
    ;
    if (params.flat != null) {
        if (typeof params.flat == "string")
            params.flat = document.getElementById(params.flat);
        if (!params.flat) {
            alert("Calendar.setup '" + params.id + "':\n  Flat specified but can't find parent.");
            return false;
        }
        var cal = new Zapatec.Calendar(params.firstDay, params.date, params.onSelect || onSelect);
        cal.id = params.id;
        cal.disableFdowClick = params.disableFdowChange;
        cal.showsOtherMonths = params.showOthers;
        cal.showsTime = params.showsTime;
        cal.time24 = (params.timeFormat == "24");
        cal.timeInterval = params.timeInterval;
        cal.params = params;
        cal.weekNumbers = params.weekNumbers;
        cal.sortOrder = params.sortOrder.toLowerCase();
        cal.setRange(params.range[0], params.range[1]);
        cal.setDateStatusHandler(params.dateStatusFunc);
        cal.getDateText = params.dateText;
        cal.numberMonths = params.numberMonths;
        cal.controlMonth = params.controlMonth;
        cal.vertical = params.vertical;
        cal.yearStep = params.step;
        cal.monthsInRow = params.monthsInRow;
        cal.helpButton = !params.noHelp;
        cal.closeButton = !params.noCloseButton;
        cal.yearNav = !params.disableYearNav;
        if (params.ifFormat) {
            cal.setDateFormat(params.ifFormat);
        }
        if (params.inputField && params.inputField.type == "text" && typeof params.inputField.value == "string") {
            cal.parseDate(params.inputField.value);
        }
        if (params.multiple) {
            cal.setMultipleDates(params.multiple);
        }
        cal.create(params.flat);
        cal.show();
        return cal;
    }
    var triggerEl = params.button || params.displayArea || params.inputField;
    if (params.canType) {
        function cancelBubble(ev) {
            ev = ev || window.event;
            if (Zapatec.is_ie) {
                ev.cancelBubble = true;
            }
            else {
                ev.stopPropagation();
            }
        }
        Zapatec.Utils.addEvent(params.inputField, "mousedown", cancelBubble);
        Zapatec.Utils.addEvent(params.inputField, "keydown", cancelBubble);
        Zapatec.Utils.addEvent(params.inputField, "keypress", cancelBubble);
        Zapatec.Utils.addEvent(params.inputField, "keyup", function(ev) {
            var format = params.inputField ? params.ifFormat : params.daFormat;
            var parsedDate = Date.parseDate(params.inputField.value, format);
            var cal = window.calendar;
            if (cal && parsedDate && !cal.hidden) {
                cal.setDate(parsedDate);
            }
        });
    }
    triggerEl["on" + params.eventName] = function() {
        var dateEl = params.inputField || params.displayArea;
        if ((!params.canType || params.inputField != triggerEl) && triggerEl.blur) {
            triggerEl.blur();
        }
        var dateFmt = params.inputField ? params.ifFormat : params.daFormat;
        var mustCreate = false;
        var cal = window.calendar;
        if (params.canType && (params.inputField == triggerEl) && cal && !cal.hidden) {
            return;
        }
        if (!(cal && params.cache)) {
            window.calendar = cal = new Zapatec.Calendar(params.firstDay, params.date, params.onSelect || onSelect, params.onClose || function(cal) {
                if (params.cache)
                    cal.hide();
                else
                    cal.destroy();
            });
            cal.id = params.id;
            cal.disableFdowClick = params.disableFdowChange;
            cal.showsTime = params.showsTime;
            cal.time24 = (params.timeFormat == "24");
            cal.timeInterval = params.timeInterval;
            cal.weekNumbers = params.weekNumbers;
            cal.numberMonths = params.numberMonths;
            cal.controlMonth = params.controlMonth;
            cal.vertical = params.vertical;
            cal.monthsInRow = params.monthsInRow;
            cal.historyDateFormat = params.ifFormat || params.daFormat;
            cal.helpButton = !params.noHelp;
            cal.disableDrag = params.disableDrag;
            cal.closeButton = !params.noCloseButton;
            cal.yearNav = !params.disableYearNav;
            cal.sortOrder = params.sortOrder.toLowerCase();
            mustCreate = true;
        }
        else {
            if (params.date)
                cal.setDate(params.date);
            cal.hide();
        }
        if (params.multiple) {
            cal.setMultipleDates(params.multiple);
        }
        cal.showsOtherMonths = params.showOthers;
        cal.yearStep = params.step;
        cal.setRange(params.range[0], params.range[1]);
        cal.params = params;
        cal.setDateStatusHandler(params.dateStatusFunc);
        cal.getDateText = params.dateText;
        cal.setDateFormat(dateFmt);
        if (mustCreate)
            cal.create();
        if (dateEl) {
            var dateValue;
            if (dateEl.value) {
                dateValue = dateEl.value;
            }
            else {
                dateValue = dateEl.innerHTML;
            }
            if (dateValue != "") {
                var parsedDate = Date.parseDate(dateEl.value || dateEl.innerHTML, dateFmt);
                if (parsedDate != null) {
                    cal.setDate(parsedDate);
                }
            }
        }
        if (!params.position)
            cal.showAtElement(params.button || params.displayArea || params.inputField, params.align);
        else
            cal.showAt(params.position[0], params.position[1]);
        return false;
    };
    if (params.closeEventName) {
        triggerEl["on" + params.closeEventName] = function() {
            if (window.calendar)
                window.calendar.callCloseHandler();
        };
    }
    return cal;
};

// $Id: calendar-en.js 6573 2007-03-09 08:36:16Z slip $
// ** I18N

// Calendar EN language
// Author: Mihai Bazon, <mihai_bazon@yahoo.com>
// Encoding: any
// Distributed under the same terms as the calendar itself.

// For translators: please use UTF-8 if possible.  We strongly believe that
// Unicode is the answer to a real internationalized world.  Also please
// include your contact information in the header, as can be seen above.

// full day names
Zapatec.Calendar._DN = new Array
("Sunday",
 "Monday",
 "Tuesday",
 "Wednesday",
 "Thursday",
 "Friday",
 "Saturday",
 "Sunday");

// Please note that the following array of short day names (and the same goes
// for short month names, _SMN) isn't absolutely necessary.  We give it here
// for exemplification on how one can customize the short day names, but if
// they are simply the first N letters of the full name you can simply say:
//
//   Zapatec.Calendar._SDN_len = N; // short day name length
//   Zapatec.Calendar._SMN_len = N; // short month name length
//
// If N = 3 then this is not needed either since we assume a value of 3 if not
// present, to be compatible with translation files that were written before
// this feature.

// short day names
Zapatec.Calendar._SDN = new Array
("Sun",
 "Mon",
 "Tue",
 "Wed",
 "Thu",
 "Fri",
 "Sat",
 "Sun");

// First day of the week. "0" means display Sunday first, "1" means display
// Monday first, etc.
Zapatec.Calendar._FD = 0;

// full month names
Zapatec.Calendar._MN = new Array
("January",
 "February",
 "March",
 "April",
 "May",
 "June",
 "July",
 "August",
 "September",
 "October",
 "November",
 "December");

// short month names
Zapatec.Calendar._SMN = new Array
("Jan",
 "Feb",
 "Mar",
 "Apr",
 "May",
 "Jun",
 "Jul",
 "Aug",
 "Sep",
 "Oct",
 "Nov",
 "Dec");

// tooltips
Zapatec.Calendar._TT_en = Zapatec.Calendar._TT = {};
Zapatec.Calendar._TT["INFO"] = "About the calendar";

Zapatec.Calendar._TT["ABOUT"] =
"DHTML Date/Time Selector\n" +
"(c) zapatec.com 2002-2007\n" + // don't translate this this ;-)
"For latest version visit: http://www.zapatec.com/\n" +
"\n\n" +
"Date selection:\n" +
"- Use the \xab, \xbb buttons to select year\n" +
"- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" +
"- Hold mouse button on any of the above buttons for faster selection.";
Zapatec.Calendar._TT["ABOUT_TIME"] = "\n\n" +
"Time selection:\n" +
"- Click on any of the time parts to increase it\n" +
"- or Shift-click to decrease it\n" +
"- or click and drag for faster selection.";

Zapatec.Calendar._TT["PREV_YEAR"] = "Prev. year (hold for menu)";
Zapatec.Calendar._TT["PREV_MONTH"] = "Prev. month (hold for menu)";
Zapatec.Calendar._TT["GO_TODAY"] = "Go Today (hold for history)";
Zapatec.Calendar._TT["NEXT_MONTH"] = "Next month (hold for menu)";
Zapatec.Calendar._TT["NEXT_YEAR"] = "Next year (hold for menu)";
Zapatec.Calendar._TT["SEL_DATE"] = "Select date";
Zapatec.Calendar._TT["DRAG_TO_MOVE"] = "Drag to move";
Zapatec.Calendar._TT["PART_TODAY"] = " (today)";

// the following is to inform that "%s" is to be the first day of week
// %s will be replaced with the day name.
Zapatec.Calendar._TT["DAY_FIRST"] = "Display %s first";

// This may be locale-dependent.  It specifies the week-end days, as an array
// of comma-separated numbers.  The numbers are from 0 to 6: 0 means Sunday, 1
// means Monday, etc.
Zapatec.Calendar._TT["WEEKEND"] = "0,6";

Zapatec.Calendar._TT["CLOSE"] = "Close";
Zapatec.Calendar._TT["TODAY"] = "Today";
Zapatec.Calendar._TT["TIME_PART"] = "(Shift-)Click or drag to change value";

// date formats
Zapatec.Calendar._TT["DEF_DATE_FORMAT"] = "%m/%d/%Y";
Zapatec.Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e";

Zapatec.Calendar._TT["WK"] = "wk";
Zapatec.Calendar._TT["TIME"] = "Time:";

Zapatec.Calendar._TT["E_RANGE"] = "Outside the range";

Zapatec.Calendar._TT._AMPM = {am : "am", pm : "pm"};

/* Preserve data */
	if(Zapatec.Calendar._DN) Zapatec.Calendar._TT._DN = Zapatec.Calendar._DN;
	if(Zapatec.Calendar._SDN) Zapatec.Calendar._TT._SDN = Zapatec.Calendar._SDN;
	if(Zapatec.Calendar._SDN_len) Zapatec.Calendar._TT._SDN_len = Zapatec.Calendar._SDN_len;
	if(Zapatec.Calendar._MN) Zapatec.Calendar._TT._MN = Zapatec.Calendar._MN;
	if(Zapatec.Calendar._SMN) Zapatec.Calendar._TT._SMN = Zapatec.Calendar._SMN;
	if(Zapatec.Calendar._SMN_len) Zapatec.Calendar._TT._SMN_len = Zapatec.Calendar._SMN_len;
	Zapatec.Calendar._DN = Zapatec.Calendar._SDN = Zapatec.Calendar._SDN_len = Zapatec.Calendar._MN = Zapatec.Calendar._SMN = Zapatec.Calendar._SMN_len = null


var UserPicker = Class.create();
UserPicker.prototype = {

    initialize: function(fieldAutocomplete, fieldAutocompleteChoices, fieldValues, fieldContainer, fieldRowIDPrefix) {
        this.fieldAutocomplete = fieldAutocomplete ? fieldAutocomplete : 'autocomplete';
        this.fieldAutocompleteChoices = fieldAutocompleteChoices ? fieldAutocompleteChoices : 'user_choices';
        this.fieldValues = fieldValues ? fieldValues : 'username';
        this.fieldContainer = fieldContainer ? fieldContainer : 'blog-authors';
        this.fieldRowIDPrefix = fieldRowIDPrefix ? fieldRowIDPrefix : 'jive-user-';
        this.startAutoCompleter(this);
    },

    startAutoCompleter: function(instance) {
        var ac = new Ajax.Autocompleter(this.fieldAutocomplete, this.fieldAutocompleteChoices, "view-people-autocomplete.jspa?resultTypes=COMMUNITY&sort=relevance&start=0&view=search&usernameEnabled=true&nameEnabled=true&emailEnabled=true", {tokens: [',', ' '], updateElement: function(
                selected)
        {
            instance.addUser(Element.collectTextNodesIgnoreClass(selected, 'informal'), instance);
        }, paramName: "query", minChars: 1});
        ac.getToken = function() {
            var bounds = this.getTokenBounds();
            return this.element.value.substring(bounds[0], bounds[1]).strip().toLowerCase();    //compare usernames using lower case
        }
    },

    loadWin: function(elem, multiple) {
        this.win = window.open("user-picker!input.jspa?selectedUsers=" + encodeURIComponent(elem.value) + "&multiple="
                + multiple + "&element=" + elem.getAttributeNode('name').value + "&form="
                + elem.form.getAttributeNode('name').value, "", "menubar=yes,location=no,personalbar=no,scrollbars=yes,width=600,height=800,resizable");
    },

    removeUser: function(user) {
        if (typeof user != 'string') {
            user = Event.element(user).id.gsub('jive-remove-user-link-', '');
        }

        if (user && user.length > 0) {
            Element.remove(this.fieldRowIDPrefix + user);
            var vals = $(this.fieldValues).value.split(",");
            vals[vals.indexOf(user)] = null;    //remove user from array
            vals = vals.compact();  //get rid of any nulls
            $(this.fieldValues).value = vals.join(",");
        }
    },

    addUser: function(user, instance) {
        if (!instance) {
            instance = this;
        }

        if (!user) {
            user = $F(instance.fieldAutocomplete);
        }
        else
        {
            user = user.strip();
        }

        if (user.length > 0) {
            if (user.indexOf(',') > -1) {
                var users = new Array();
                users = user.split(',');
                for (var i = 0; i < users.length; i++) {
                    instance.addUser(users[i]);
                }
            }
            else
            {
                if (!$(instance.fieldRowIDPrefix + user)) {
                    var removeLink = this._addUserElement(user);

                    $(instance.fieldValues).value += "," + user;
                    if (removeLink) {
                        removeLink.observe('click', this.removeUser.bindAsEventListener(this), false);
                    }
                }
                var acAr = $F(instance.fieldAutocomplete).split(",");
                if (acAr.length > 0) acAr.pop();     //last value is the selected value, so drop it
                $(instance.fieldAutocomplete).value = acAr.join(",");
                Field.focus(instance.fieldAutocomplete);
                new Effect.Highlight(instance.fieldRowIDPrefix + user);
            }
        }
    },
    _addUserElement: function(user) {
        var removeLink = new Element('a', {href: 'javascript:;', id: 'jive-remove-user-link-' + user}).update('[x]');
        $(this.fieldContainer).insert(new Element('tr', {id: this.fieldRowIDPrefix + user})
                .insert(new Element('td').insert(new Element('img', {src: '../people/' + encodeURI(user) + '/avatar/16.png'})))
                .insert(new Element('td', {width: 200}).update(user))
                .insert(new Element('td').insert(removeLink)));

        return removeLink;
    }

}

var JiveProjectChooser = Class.create();
JiveProjectChooser.prototype = {
    initialize: function(chooserURL) {
        this.chooserURL = chooserURL;
    },

    handleChange: function(elem) {
        if ($F(elem) == -1) {
            this.openChooser(elem.id);
        }
    },

    openChooser: function(id) {
        this.win = window.open(this.chooserURL + "?id=" + id,"","menubar=yes,location=no,personalbar=no,scrollbars=yes,width=600,height=800,resizable");
    },

    addProject: function(id, value, description) {
        var select = $(id);
        var options = $A(select.getElementsByTagName("option"));
        var opt = options.find(function(option){
            return (option.value == value);
        });
        if (!opt) {
            // option doesn't exist in the list, so create it
            var newOption = new Element("option", {value: value});
            newOption.update(description);
            
            // insert before other, if exists
            var other = $(options.find(function(option) {
                return (option.value == '');
            }));
            other.insert({after: newOption});

            select.selectedIndex = -1;
            newOption.selected = true;
        } else {
            select.selectedIndex = -1;
            opt.selected = true;
        }
        select.focus();
    }
}

var JiveGroupChooser = Class.create();
JiveGroupChooser.prototype = {
    initialize: function(chooserURL) {
        this.chooserURL = chooserURL;
    },

    handleChange: function(elem) {
        if ($F(elem) == -1) {
            this.openChooser(elem.id);
        }
    },

    openChooser: function(id) {
        this.win = window.open(this.chooserURL + "?id=" + id,"","menubar=yes,location=no,personalbar=no,scrollbars=yes,width=600,height=800,resizable");
    },

    addGroup: function(id, value, description) {
        var select = $(id);
        var options = $A(select.getElementsByTagName("option"));
        var opt = options.find(function(option){
            return (option.value == value);
        });
        if (!opt) {
            // option doesn't exist in the list, so create it
            var newOption = new Element("option", {value: value});
            newOption.update(description);
            
            // insert before other, if exists
            var other = $(options.find(function(option) {
                return (option.value == '');
            }));
            other.insert({after: newOption});

            select.selectedIndex = -1;
            newOption.selected = true;
        } else {
            select.selectedIndex = -1;
            opt.selected = true;
        }
        select.focus();
    }
}

// FancyZoomHTML.js - v1.0
// Used to draw necessary HTML elements for FancyZoom
//
// Copyright (c) 2008 Cabel Sasser / Panic Inc
// All rights reserved.

function insertZoomHTML() {

	// All of this junk creates the three <div>'s used to hold the closebox, image, and zoom shadow.
	
	var inBody = document.getElementsByTagName("body").item(0);
	
	// WAIT SPINNER
	
	var inSpinbox = document.createElement("div");
	inSpinbox.setAttribute('id', 'ZoomSpin');
	inSpinbox.style.position = 'absolute';
	inSpinbox.style.left = '10px';
	inSpinbox.style.top = '10px';
	inSpinbox.style.visibility = 'hidden';
	inSpinbox.style.zIndex = '525';
	inBody.insertBefore(inSpinbox, inBody.firstChild);
	
	var inSpinImage = document.createElement("img");
	inSpinImage.setAttribute('id', 'SpinImage');
	inSpinImage.setAttribute('src', zoomImagesURI+'zoom-spin-1.png');
	inSpinbox.appendChild(inSpinImage);
	
	// ZOOM IMAGE
	//
	// <div id="ZoomBox">
	//   <a href="javascript:zoomOut();"><img src="/images/spacer.gif" id="ZoomImage" border="0"></a> <!-- THE IMAGE -->
	//   <div id="ZoomClose">
	//     <a href="javascript:zoomOut();"><img src="/images/closebox.png" width="30" height="30" border="0"></a>
	//   </div>
	// </div>
	
	var inZoombox = document.createElement("div");
	inZoombox.setAttribute('id', 'ZoomBox');
	
	inZoombox.style.position = 'absolute'; 
	inZoombox.style.left = '10px';
	inZoombox.style.top = '10px';
	inZoombox.style.visibility = 'hidden';
	inZoombox.style.zIndex = '499';
	
	inBody.insertBefore(inZoombox, inSpinbox.nextSibling);
	
	var inImage1 = document.createElement("img");
	inImage1.onclick = function (event) { zoomOut(this, event); return false; };	
	inImage1.setAttribute('src',zoomImagesURI+'spacer.gif');
	inImage1.setAttribute('id','ZoomImage');
	inImage1.setAttribute('border', '0');
	// inImage1.setAttribute('onMouseOver', 'zoomMouseOver();')
	// inImage1.setAttribute('onMouseOut', 'zoomMouseOut();')
	
	// This must be set first, so we can later test it using webkitBoxShadow.
	inImage1.setAttribute('style', '-webkit-box-shadow: '+shadowSettings+'0.0)');
	inImage1.style.display = 'block';
	inImage1.style.width = '10px';
	inImage1.style.height = '10px';
	inImage1.style.cursor = 'pointer'; // -webkit-zoom-out?
	inZoombox.appendChild(inImage1);

	var inClosebox = document.createElement("div");
	inClosebox.setAttribute('id', 'ZoomClose');
	inClosebox.style.position = 'absolute';
	
	// In MSIE, we need to put the close box inside the image.
	// It's 2008 and I'm having to do a browser detect? Sigh.
	if (browserIsIE) {
		inClosebox.style.left = '-1px';
		inClosebox.style.top = '0px';	
	} else {
		inClosebox.style.left = '-15px';
		inClosebox.style.top = '-15px';
	}
	
	inClosebox.style.visibility = 'hidden';
	inZoombox.appendChild(inClosebox);
		
	var inImage2 = document.createElement("img");
	inImage2.onclick = function (event) { zoomOut(this, event); return false; };	
	inImage2.setAttribute('src',zoomImagesURI+'closebox.png');		
	inImage2.setAttribute('width','30');
	inImage2.setAttribute('height','30');
	inImage2.setAttribute('border','0');
	inImage2.style.cursor = 'pointer';		
	inClosebox.appendChild(inImage2);
	
	// SHADOW
	// Only draw the table-based shadow if the programatic webkitBoxShadow fails!
	// Also, don't draw it if we're IE -- it wouldn't look quite right anyway.
	
	if (! document.getElementById('ZoomImage').style.webkitBoxShadow && ! browserIsIE) {

		// SHADOW BASE
		
		var inFixedBox = document.createElement("div");
		inFixedBox.setAttribute('id', 'ShadowBox');
		inFixedBox.style.position = 'absolute'; 
		inFixedBox.style.left = '50px';
		inFixedBox.style.top = '50px';
		inFixedBox.style.width = '100px';
		inFixedBox.style.height = '100px';
		inFixedBox.style.visibility = 'hidden';
		inFixedBox.style.zIndex = '498';
		inBody.insertBefore(inFixedBox, inZoombox.nextSibling);	
	
		// SHADOW
		// Now, the shadow table. Skip if not compatible, or irrevelant with -box-shadow.
		
		// <div id="ShadowBox"><table border="0" width="100%" height="100%" cellpadding="0" cellspacing="0"> X
		//   <tr height="25">
		//   <td width="27"><img src="/images/zoom-shadow1.png" width="27" height="25"></td>
		//   <td background="/images/zoom-shadow2.png">&nbsp;</td>
		//   <td width="27"><img src="/images/zoom-shadow3.png" width="27" height="25"></td>
		//   </tr>
		
		var inShadowTable = document.createElement("table");
		inShadowTable.setAttribute('border', '0');
		inShadowTable.setAttribute('width', '100%');
		inShadowTable.setAttribute('height', '100%');
		inShadowTable.setAttribute('cellpadding', '0');
		inShadowTable.setAttribute('cellspacing', '0');
		inFixedBox.appendChild(inShadowTable);

		var inShadowTbody = document.createElement("tbody");	// Needed for IE (for HTML4).
		inShadowTable.appendChild(inShadowTbody);
		
		var inRow1 = document.createElement("tr");
		inRow1.style.height = '25px';
		inShadowTbody.appendChild(inRow1);
		
		var inCol1 = document.createElement("td");
		inCol1.style.width = '27px';
		inRow1.appendChild(inCol1);  
		var inShadowImg1 = document.createElement("img");
		inShadowImg1.setAttribute('src', zoomImagesURI+'zoom-shadow1.png');
		inShadowImg1.setAttribute('width', '27');
		inShadowImg1.setAttribute('height', '25');
		inShadowImg1.style.display = 'block';
		inCol1.appendChild(inShadowImg1);
		
		var inCol2 = document.createElement("td");
		inCol2.setAttribute('background', zoomImagesURI+'zoom-shadow2.png');
        inRow1.appendChild(inCol2);
		// inCol2.innerHTML = '<img src=';
		var inSpacer1 = document.createElement("img");
		inSpacer1.setAttribute('src',zoomImagesURI+'spacer.gif');
		inSpacer1.setAttribute('height', '1');
		inSpacer1.setAttribute('width', '1');
		inSpacer1.style.display = 'block';
		inCol2.appendChild(inSpacer1);
		
		var inCol3 = document.createElement("td");
		inCol3.style.width = '27px';
		inRow1.appendChild(inCol3);  
		var inShadowImg3 = document.createElement("img");
		inShadowImg3.setAttribute('src', zoomImagesURI+'zoom-shadow3.png');
		inShadowImg3.setAttribute('width', '27');
		inShadowImg3.setAttribute('height', '25');
		inShadowImg3.style.display = 'block';
		inCol3.appendChild(inShadowImg3);
		
		//   <tr>
		//   <td background="/images/zoom-shadow4.png">&nbsp;</td>
		//   <td bgcolor="#ffffff">&nbsp;</td>
		//   <td background="/images/zoom-shadow5.png">&nbsp;</td>
		//   </tr>
		
		inRow2 = document.createElement("tr");
		inShadowTbody.appendChild(inRow2);
		
		var inCol4 = document.createElement("td");
		inCol4.setAttribute('background', zoomImagesURI+'zoom-shadow4.png');
		inRow2.appendChild(inCol4);
		// inCol4.innerHTML = '&nbsp;';
		var inSpacer2 = document.createElement("img");
		inSpacer2.setAttribute('src',zoomImagesURI+'spacer.gif');
		inSpacer2.setAttribute('height', '1');
		inSpacer2.setAttribute('width', '1');
		inSpacer2.style.display = 'block';
		inCol4.appendChild(inSpacer2);
		
		var inCol5 = document.createElement("td");
		inCol5.setAttribute('bgcolor', '#ffffff');
		inRow2.appendChild(inCol5);
		// inCol5.innerHTML = '&nbsp;';
		var inSpacer3 = document.createElement("img");
		inSpacer3.setAttribute('src',zoomImagesURI+'spacer.gif');
		inSpacer3.setAttribute('height', '1');
		inSpacer3.setAttribute('width', '1');
		inSpacer3.style.display = 'block';
		inCol5.appendChild(inSpacer3);
		
		var inCol6 = document.createElement("td");
		inCol6.setAttribute('background', zoomImagesURI+'zoom-shadow5.png');
		inRow2.appendChild(inCol6);
		// inCol6.innerHTML = '&nbsp;';
		var inSpacer4 = document.createElement("img");
		inSpacer4.setAttribute('src',zoomImagesURI+'spacer.gif');
		inSpacer4.setAttribute('height', '1');
		inSpacer4.setAttribute('width', '1');
		inSpacer4.style.display = 'block';
		inCol6.appendChild(inSpacer4);
		
		//   <tr height="26">
		//   <td width="27"><img src="/images/zoom-shadow6.png" width="27" height="26"</td>
		//   <td background="/images/zoom-shadow7.png">&nbsp;</td>
		//   <td width="27"><img src="/images/zoom-shadow8.png" width="27" height="26"></td>
		//   </tr>  
		// </table>
		
		var inRow3 = document.createElement("tr");
		inRow3.style.height = '26px';
		inShadowTbody.appendChild(inRow3);
		
		var inCol7 = document.createElement("td");
		inCol7.style.width = '27px';
		inRow3.appendChild(inCol7);
		var inShadowImg7 = document.createElement("img");
		inShadowImg7.setAttribute('src', zoomImagesURI+'zoom-shadow6.png');
		inShadowImg7.setAttribute('width', '27');
		inShadowImg7.setAttribute('height', '26');
		inShadowImg7.style.display = 'block';
		inCol7.appendChild(inShadowImg7);
		
		var inCol8 = document.createElement("td");
		inCol8.setAttribute('background', zoomImagesURI+'zoom-shadow7.png');
		inRow3.appendChild(inCol8);  
		// inCol8.innerHTML = '&nbsp;';
		var inSpacer5 = document.createElement("img");
		inSpacer5.setAttribute('src',zoomImagesURI+'spacer.gif');
		inSpacer5.setAttribute('height', '1');
		inSpacer5.setAttribute('width', '1');
		inSpacer5.style.display = 'block';
		inCol8.appendChild(inSpacer5);
		
		var inCol9 = document.createElement("td");
		inCol9.style.width = '27px';
		inRow3.appendChild(inCol9);  
		var inShadowImg9 = document.createElement("img");
		inShadowImg9.setAttribute('src', zoomImagesURI+'zoom-shadow8.png');
		inShadowImg9.setAttribute('width', '27');
		inShadowImg9.setAttribute('height', '26');
		inShadowImg9.style.display = 'block';
		inCol9.appendChild(inShadowImg9);
	}

	if (includeCaption) {
	
		// CAPTION
		//
		// <div id="ZoomCapDiv" style="margin-left: 13px; margin-right: 13px;">
		// <table border="1" cellpadding="0" cellspacing="0">
		// <tr height="26">
		// <td><img src="zoom-caption-l.png" width="13" height="26"></td>
		// <td rowspan="3" background="zoom-caption-fill.png"><div id="ZoomCaption"></div></td>
		// <td><img src="zoom-caption-r.png" width="13" height="26"></td>
		// </tr>
		// </table>
		// </div>
		
		var inCapDiv = document.createElement("div");
		inCapDiv.setAttribute('id', 'ZoomCapDiv');
		inCapDiv.style.position = 'absolute'; 		
		inCapDiv.style.visibility = 'hidden';
		inCapDiv.style.marginLeft = 'auto';
		inCapDiv.style.marginRight = 'auto';
		inCapDiv.style.zIndex = '501';

		inBody.insertBefore(inCapDiv, inZoombox.nextSibling);
		
		var inCapTable = document.createElement("table");
		inCapTable.setAttribute('border', '0');
		inCapTable.setAttribute('cellPadding', '0');	// Wow. These honestly need to
		inCapTable.setAttribute('cellSpacing', '0');	// be intercapped to work in IE. WTF?
		inCapDiv.appendChild(inCapTable);
		
		var inTbody = document.createElement("tbody");	// Needed for IE (for HTML4).
		inCapTable.appendChild(inTbody);
		
		var inCapRow1 = document.createElement("tr");
		inTbody.appendChild(inCapRow1);
		
		var inCapCol1 = document.createElement("td");
		inCapCol1.setAttribute('align', 'right');
		inCapRow1.appendChild(inCapCol1);
		var inCapImg1 = document.createElement("img");
		inCapImg1.setAttribute('src', zoomImagesURI+'zoom-caption-l.png');
		inCapImg1.setAttribute('width', '13');
		inCapImg1.setAttribute('height', '26');
		inCapImg1.style.display = 'block';
		inCapCol1.appendChild(inCapImg1);
		
		var inCapCol2 = document.createElement("td");
		inCapCol2.setAttribute('background', zoomImagesURI+'zoom-caption-fill.png');
		inCapCol2.setAttribute('id', 'ZoomCaption');
		inCapCol2.setAttribute('valign', 'middle');
		inCapCol2.style.fontSize = '14px';
		inCapCol2.style.fontFamily = 'Helvetica';
		inCapCol2.style.fontWeight = 'bold';
		inCapCol2.style.color = '#ffffff';
		inCapCol2.style.textShadow = '0px 2px 4px #000000';
		inCapCol2.style.whiteSpace = 'nowrap';
		inCapRow1.appendChild(inCapCol2);

		var inCapCol3 = document.createElement("td");
		inCapRow1.appendChild(inCapCol3);
		var inCapImg2 = document.createElement("img");
		inCapImg2.setAttribute('src', zoomImagesURI+'zoom-caption-r.png');
		inCapImg2.setAttribute('width', '13');
		inCapImg2.setAttribute('height', '26');
		inCapImg2.style.display = 'block';
		inCapCol3.appendChild(inCapImg2);
	}
}

// FancyZoom.js - v1.1 - http://www.fancyzoom.com
//
// Copyright (c) 2008 Cabel Sasser / Panic Inc
// All rights reserved.
// 
//     Requires: FancyZoomHTML.js
// Instructions: Include JS files in page, call setupZoom() in onLoad. That's it!
//               Any <a href> links to images will be updated to zoom inline.
//               Add rel="nozoom" to your <a href> to disable zooming for an image.
// 
// Redistribution and use of this effect in source form, with or without modification,
// are permitted provided that the following conditions are met:
// 
// * USE OF SOURCE ON COMMERCIAL (FOR-PROFIT) WEBSITE REQUIRES ONE-TIME LICENSE FEE PER DOMAIN.
//   Reasonably priced! Visit www.fancyzoom.com for licensing instructions. Thanks!
//
// * Non-commercial (personal) website use is permitted without license/payment!
//
// * Redistribution of source code must retain the above copyright notice,
//   this list of conditions and the following disclaimer.
//
// * Redistribution of source code and derived works cannot be sold without specific
//   written prior permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

var includeCaption = true; // Turn on the "caption" feature, and write out the caption HTML
var zoomTime       = 5;    // Milliseconds between frames of zoom animation
var zoomSteps      = 15;   // Number of zoom animation frames
var includeFade    = 1;    // Set to 1 to fade the image in / out as it zooms
var minBorder      = 90;   // Amount of padding between large, scaled down images, and the window edges
var shadowSettings = '0px 5px 25px rgba(0, 0, 0, '; // Blur, radius, color of shadow for compatible browsers

var zoomImagesURI   = '/images-global/zoom/'; // Location of the zoom and shadow images

// Init. Do not add anything below this line, unless it's something awesome.

var myWidth = 0, myHeight = 0, myScroll = 0; myScrollWidth = 0; myScrollHeight = 0;
var zoomOpen = false, preloadFrame = 1, preloadActive = false, preloadTime = 0, imgPreload = new Image(), preloadFail = false;
var preloadAnimTimer = 0;

var zoomActive = new Array(); var zoomTimer  = new Array(); 
var zoomOrigW  = new Array(); var zoomOrigH  = new Array();
var zoomOrigX  = new Array(); var zoomOrigY  = new Array();

var zoomID         = "ZoomBox";
var theID          = "ZoomImage";
var zoomCaption    = "ZoomCaption";
var zoomCaptionDiv = "ZoomCapDiv";

if (navigator.userAgent.indexOf("MSIE") != -1) {
	var browserIsIE = true;
}

// Zoom: Setup The Page! Called in your <body>'s onLoad handler.

function setupZoom() {
	prepZooms();
	insertZoomHTML();
	zoomdiv = document.getElementById(zoomID);  
	zoomimg = document.getElementById(theID);
}

// Zoom: Inject Javascript functions into hrefs pointing to images, one by one!
// Skip any href that contains a rel="nozoom" tag.
// This is done at page load time via an onLoad() handler.

function prepZooms() {
	if (! document.getElementsByTagName) {
		return;
	}
	var links = document.getElementsByTagName("a");
	for (i = 0; i < links.length; i++) {
		if (links[i].getAttribute("href")) {
			if (links[i].getAttribute("href").search(/(.*)\.(jpg|jpeg|gif|png|bmp|tif|tiff)/gi) != -1) {
				if (links[i].getAttribute("rel") != "nozoom") {
					links[i].onclick = function (event) { return zoomClick(this, event); };
					links[i].onmouseover = function () { zoomPreload(this); };
				}
			}
		}
	}
}

// Zoom: Load an image into an image object. When done loading, function sets preloadActive to false,
// so other bits know that they can proceed with the zoom.
// Preloaded image is stored in imgPreload and swapped out in the zoom function.

function zoomPreload(from) {

	var theimage = from.getAttribute("href");

	// Only preload if we have to, i.e. the image isn't this image already

	if (imgPreload.src.indexOf(from.getAttribute("href").substr(from.getAttribute("href").lastIndexOf("/"))) == -1) {
		preloadActive = true;
		imgPreload = new Image();

		// Set a function to fire when the preload is complete, setting flags along the way.

		imgPreload.onload = function() {
			preloadActive = false;
		}
		
		imgPreload.onerror = function() {
			preloadFail = true; 
			if (preloadActive) {
				document.getElementById("ZoomSpin").style.visibility = "hidden";    
				clearInterval(preloadAnimTimer);
				preloadAnimTimer = 0;
			}
		}
		// Load it!
		imgPreload.src = theimage;
	}
}

// Zoom: Start the preloading animation cycle.

function preloadAnimStart() {
	preloadTime = new Date();
	document.getElementById("ZoomSpin").style.left = (myWidth / 2) + 'px';
	document.getElementById("ZoomSpin").style.top = ((myHeight / 2) + myScroll) + 'px';
	document.getElementById("ZoomSpin").style.visibility = "visible";	
	preloadFrame = 1;
	document.getElementById("SpinImage").src = zoomImagesURI+'zoom-spin-'+preloadFrame+'.png';  
	preloadAnimTimer = setInterval("preloadAnim()", 100);
}

// Zoom: Display and ANIMATE the jibber-jabber widget. Once preloadActive is false, bail and zoom it up!

function preloadAnim(from) {
	if (preloadActive != false && preloadFail == false) {
		document.getElementById("SpinImage").src = zoomImagesURI+'zoom-spin-'+preloadFrame+'.png';
		preloadFrame++;
		if (preloadFrame > 12) preloadFrame = 1;
	} else if (preloadFail == false){
		document.getElementById("ZoomSpin").style.visibility = "hidden";    
		clearInterval(preloadAnimTimer);
		preloadAnimTimer = 0;
		zoomIn(preloadFrom);
	} else {
		document.getElementById("ZoomSpin").style.visibility = "hidden";    
		clearInterval(preloadAnimTimer);
		preloadAnimTimer = 0;
	}
}

// ZOOM CLICK: We got a click! Should we do the zoom? Or wait for the preload to complete?
// todo?: Double check that imgPreload src = clicked src

function zoomClick(from, evt) {

	var shift = getShift(evt);

	// Check for Command / Alt key. If pressed, pass them through -- don't zoom!
	if (! evt && window.event && (window.event.metaKey || window.event.altKey)) {
		return true;
	} else if (evt && (evt.metaKey|| evt.altKey)) {
		return true;
	}

	// Get browser dimensions
	getSize();

	// If preloading still, wait, and display the spinner.
	if (preloadActive == true) {
		// But only display the spinner if it's not already being displayed!
		if (preloadAnimTimer == 0) {
			preloadFrom = from;
			preloadAnimStart();	
		}
	} else {
		// Otherwise, we're loaded: do the zoom!
		zoomIn(from, shift);
	}
	
	return false;
	
}

// Zoom: Move an element in to endH endW, using zoomHost as a starting point.
// "from" is an object reference to the href that spawned the zoom.

function zoomIn(from, shift) {

	zoomimg.src = from.getAttribute("href");

	// Determine the zoom settings from where we came from, the element in the <a>.
	// If there's no element in the <a>, or we can't get the width, make stuff up

	if (from.childNodes[0].width) {
		startW = from.childNodes[0].width;
		startH = from.childNodes[0].height;
		startPos = findElementPos(from.childNodes[0]);
	} else {
		startW = 50;
		startH = 12;
		startPos = findElementPos(from);
	}

	hostX = startPos[0];
	hostY = startPos[1];

	// Make up for a scrolled containing div.
	// TODO: This HAS to move into findElementPos.
	
	if (document.getElementById('scroller')) {
		hostX = hostX - document.getElementById('scroller').scrollLeft;
	}

	// Determine the target zoom settings from the preloaded image object

	endW = imgPreload.width;
	endH = imgPreload.height;

	// Start! But only if we're not zooming already!

	if (zoomActive[theID] != true) {

		// Clear everything out just in case something is already open

		if (document.getElementById("ShadowBox")) {
			document.getElementById("ShadowBox").style.visibility = "hidden";
		} else if (! browserIsIE) {
		
			// Wipe timer if shadow is fading in still
			if (fadeActive["ZoomImage"]) {
				clearInterval(fadeTimer["ZoomImage"]);
				fadeActive["ZoomImage"] = false;
				fadeTimer["ZoomImage"] = false;			
			}
			
			document.getElementById("ZoomImage").style.webkitBoxShadow = shadowSettings + '0.0)';			
		}
		
		document.getElementById("ZoomClose").style.visibility = "hidden";     

		// Setup the CAPTION, if existing. Hide it first, set the text.

		if (includeCaption) {
			document.getElementById(zoomCaptionDiv).style.visibility = "hidden";
			if (from.getAttribute('title') && includeCaption) {
				// Yes, there's a caption, set it up
				document.getElementById(zoomCaption).innerHTML = from.getAttribute('title');
			} else {
				document.getElementById(zoomCaption).innerHTML = "";
			}
		}

		// Store original position in an array for future zoomOut.

		zoomOrigW[theID] = startW;
		zoomOrigH[theID] = startH;
		zoomOrigX[theID] = hostX;
		zoomOrigY[theID] = hostY;

		// Now set the starting dimensions

		zoomimg.style.width = startW + 'px';
		zoomimg.style.height = startH + 'px';
		zoomdiv.style.left = hostX + 'px';
		zoomdiv.style.top = hostY + 'px';

		// Show the zooming image container, make it invisible

		if (includeFade == 1) {
			setOpacity(0, zoomID);
		}
		zoomdiv.style.visibility = "visible";

		// If it's too big to fit in the window, shrink the width and height to fit (with ratio).

		sizeRatio = endW / endH;
		if (endW > myWidth - minBorder) {
			endW = myWidth - minBorder;
			endH = endW / sizeRatio;
		}
		if (endH > myHeight - minBorder) {
			endH = myHeight - minBorder;
			endW = endH * sizeRatio;
		}

		zoomChangeX = ((myWidth / 2) - (endW / 2) - hostX);
		zoomChangeY = (((myHeight / 2) - (endH / 2) - hostY) + myScroll);
		zoomChangeW = (endW - startW);
		zoomChangeH = (endH - startH);
		
		// Shift key?
	
		if (shift) {
			tempSteps = zoomSteps * 7;
		} else {
			tempSteps = zoomSteps;
		}

		// Setup Zoom

		zoomCurrent = 0;

		// Setup Fade with Zoom, If Requested

		if (includeFade == 1) {
			fadeCurrent = 0;
			fadeAmount = (0 - 100) / tempSteps;
		} else {
			fadeAmount = 0;
		}

		// Do It!
		
		zoomTimer[theID] = setInterval("zoomElement('"+zoomID+"', '"+theID+"', "+zoomCurrent+", "+startW+", "+zoomChangeW+", "+startH+", "+zoomChangeH+", "+hostX+", "+zoomChangeX+", "+hostY+", "+zoomChangeY+", "+tempSteps+", "+includeFade+", "+fadeAmount+", 'zoomDoneIn(zoomID)')", zoomTime);		
		zoomActive[theID] = true; 
	}
}

// Zoom it back out.

function zoomOut(from, evt) {

	// Get shift key status.
	// IE events don't seem to get passed through the function, so grab it from the window.

	if (getShift(evt)) {
		tempSteps = zoomSteps * 7;
	} else {
		tempSteps = zoomSteps;
	}	

	// Check to see if something is happening/open
  
	if (zoomActive[theID] != true) {

		// First, get rid of the shadow if necessary.

		if (document.getElementById("ShadowBox")) {
			document.getElementById("ShadowBox").style.visibility = "hidden";
		} else if (! browserIsIE) {
		
			// Wipe timer if shadow is fading in still
			if (fadeActive["ZoomImage"]) {
				clearInterval(fadeTimer["ZoomImage"]);
				fadeActive["ZoomImage"] = false;
				fadeTimer["ZoomImage"] = false;			
			}
			
			document.getElementById("ZoomImage").style.webkitBoxShadow = shadowSettings + '0.0)';			
		}

		// ..and the close box...

		document.getElementById("ZoomClose").style.visibility = "hidden";

		// ...and the caption if necessary!

		if (includeCaption && document.getElementById(zoomCaption).innerHTML != "") {
			// fadeElementSetup(zoomCaptionDiv, 100, 0, 5, 1);
			document.getElementById(zoomCaptionDiv).style.visibility = "hidden";
		}

		// Now, figure out where we came from, to get back there

		startX = parseInt(zoomdiv.style.left);
		startY = parseInt(zoomdiv.style.top);
		startW = zoomimg.width;
		startH = zoomimg.height;
		zoomChangeX = zoomOrigX[theID] - startX;
		zoomChangeY = zoomOrigY[theID] - startY;
		zoomChangeW = zoomOrigW[theID] - startW;
		zoomChangeH = zoomOrigH[theID] - startH;

		// Setup Zoom

		zoomCurrent = 0;

		// Setup Fade with Zoom, If Requested

		if (includeFade == 1) {
			fadeCurrent = 0;
			fadeAmount = (100 - 0) / tempSteps;
		} else {
			fadeAmount = 0;
		}

		// Do It!

		zoomTimer[theID] = setInterval("zoomElement('"+zoomID+"', '"+theID+"', "+zoomCurrent+", "+startW+", "+zoomChangeW+", "+startH+", "+zoomChangeH+", "+startX+", "+zoomChangeX+", "+startY+", "+zoomChangeY+", "+tempSteps+", "+includeFade+", "+fadeAmount+", 'zoomDone(zoomID, theID)')", zoomTime);	
		zoomActive[theID] = true;
	}
}

// Finished Zooming In

function zoomDoneIn(zoomdiv, theID) {

	// Note that it's open
  
	zoomOpen = true;
	zoomdiv = document.getElementById(zoomdiv);

	// Position the table shadow behind the zoomed in image, and display it

	if (document.getElementById("ShadowBox")) {

		setOpacity(0, "ShadowBox");
		shadowdiv = document.getElementById("ShadowBox");

		shadowLeft = parseInt(zoomdiv.style.left) - 13;
		shadowTop = parseInt(zoomdiv.style.top) - 8;
		shadowWidth = zoomdiv.offsetWidth + 26;
		shadowHeight = zoomdiv.offsetHeight + 26; 
	
		shadowdiv.style.width = shadowWidth + 'px';
		shadowdiv.style.height = shadowHeight + 'px';
		shadowdiv.style.left = shadowLeft + 'px';
		shadowdiv.style.top = shadowTop + 'px';

		document.getElementById("ShadowBox").style.visibility = "visible";
		fadeElementSetup("ShadowBox", 0, 100, 5);
		
	} else if (! browserIsIE) {
		// Or, do a fade of the modern shadow
		fadeElementSetup("ZoomImage", 0, .8, 5, 0, "shadow");
	}
	
	// Position and display the CAPTION, if existing
  
	if (includeCaption && document.getElementById(zoomCaption).innerHTML != "") {
		// setOpacity(0, zoomCaptionDiv);
		zoomcapd = document.getElementById(zoomCaptionDiv);
		zoomcapd.style.top = parseInt(zoomdiv.style.top) + (zoomdiv.offsetHeight + 15) + 'px';
		zoomcapd.style.left = (myWidth / 2) - (zoomcapd.offsetWidth / 2) + 'px';
		zoomcapd.style.visibility = "visible";
		// fadeElementSetup(zoomCaptionDiv, 0, 100, 5);
	}   
	
	// Display Close Box (fade it if it's not IE)

	if (!browserIsIE) setOpacity(0, "ZoomClose");
	document.getElementById("ZoomClose").style.visibility = "visible";
	if (!browserIsIE) fadeElementSetup("ZoomClose", 0, 100, 5);

	// Get keypresses
	document.onkeypress = getKey;
	
}

// Finished Zooming Out

function zoomDone(zoomdiv, theID) {

	// No longer open
  
	zoomOpen = false;

	// Clear stuff out, clean up

	zoomOrigH[theID] = "";
	zoomOrigW[theID] = "";
	document.getElementById(zoomdiv).style.visibility = "hidden";
	zoomActive[theID] == false;

	// Stop getting keypresses

	document.onkeypress = null;

}

// Actually zoom the element

function zoomElement(zoomdiv, theID, zoomCurrent, zoomStartW, zoomChangeW, zoomStartH, zoomChangeH, zoomStartX, zoomChangeX, zoomStartY, zoomChangeY, zoomSteps, includeFade, fadeAmount, execWhenDone) {

	// console.log("Zooming Step #"+zoomCurrent+ " of "+zoomSteps+" (zoom " + zoomStartW + "/" + zoomChangeW + ") (zoom " + zoomStartH + "/" + zoomChangeH + ")  (zoom " + zoomStartX + "/" + zoomChangeX + ")  (zoom " + zoomStartY + "/" + zoomChangeY + ") Fade: "+fadeAmount);
    
	// Test if we're done, or if we continue

	if (zoomCurrent == (zoomSteps + 1)) {
		zoomActive[theID] = false;
		clearInterval(zoomTimer[theID]);

		if (execWhenDone != "") {
			eval(execWhenDone);
		}
	} else {
	
		// Do the Fade!
	  
		if (includeFade == 1) {
			if (fadeAmount < 0) {
				setOpacity(Math.abs(zoomCurrent * fadeAmount), zoomdiv);
			} else {
				setOpacity(100 - (zoomCurrent * fadeAmount), zoomdiv);
			}
		}
	  
		// Calculate this step's difference, and move it!
		
		moveW = cubicInOut(zoomCurrent, zoomStartW, zoomChangeW, zoomSteps);
		moveH = cubicInOut(zoomCurrent, zoomStartH, zoomChangeH, zoomSteps);
		moveX = cubicInOut(zoomCurrent, zoomStartX, zoomChangeX, zoomSteps);
		moveY = cubicInOut(zoomCurrent, zoomStartY, zoomChangeY, zoomSteps);
	
		document.getElementById(zoomdiv).style.left = moveX + 'px';
		document.getElementById(zoomdiv).style.top = moveY + 'px';
		zoomimg.style.width = moveW + 'px';
		zoomimg.style.height = moveH + 'px';
	
		zoomCurrent++;
		
		clearInterval(zoomTimer[theID]);
		zoomTimer[theID] = setInterval("zoomElement('"+zoomdiv+"', '"+theID+"', "+zoomCurrent+", "+zoomStartW+", "+zoomChangeW+", "+zoomStartH+", "+zoomChangeH+", "+zoomStartX+", "+zoomChangeX+", "+zoomStartY+", "+zoomChangeY+", "+zoomSteps+", "+includeFade+", "+fadeAmount+", '"+execWhenDone+"')", zoomTime);
	}
}

// Zoom Utility: Get Key Press when image is open, and act accordingly

function getKey(evt) {
	if (! evt) {
		theKey = event.keyCode;
	} else {
		theKey = evt.keyCode;
	}

	if (theKey == 27) { // ESC
		zoomOut(this, evt);
	}
}

////////////////////////////
//
// FADE Functions
//

function fadeOut(elem) {
	if (elem.id) {
		fadeElementSetup(elem.id, 100, 0, 10);
	}
}

function fadeIn(elem) {
	if (elem.id) {
		fadeElementSetup(elem.id, 0, 100, 10);	
	}
}

// Fade: Initialize the fade function

var fadeActive = new Array();
var fadeQueue  = new Array();
var fadeTimer  = new Array();
var fadeClose  = new Array();
var fadeMode   = new Array();

function fadeElementSetup(theID, fdStart, fdEnd, fdSteps, fdClose, fdMode) {

	// alert("Fading: "+theID+" Steps: "+fdSteps+" Mode: "+fdMode);

	if (fadeActive[theID] == true) {
		// Already animating, queue up this command
		fadeQueue[theID] = new Array(theID, fdStart, fdEnd, fdSteps);
	} else {
		fadeSteps = fdSteps;
		fadeCurrent = 0;
		fadeAmount = (fdStart - fdEnd) / fadeSteps;
		fadeTimer[theID] = setInterval("fadeElement('"+theID+"', '"+fadeCurrent+"', '"+fadeAmount+"', '"+fadeSteps+"')", 15);
		fadeActive[theID] = true;
		fadeMode[theID] = fdMode;
		
		if (fdClose == 1) {
			fadeClose[theID] = true;
		} else {
			fadeClose[theID] = false;
		}
	}
}

// Fade: Do the fade. This function will call itself, modifying the parameters, so
// many instances can run concurrently. Can fade using opacity, or fade using a box-shadow.

function fadeElement(theID, fadeCurrent, fadeAmount, fadeSteps) {

	if (fadeCurrent == fadeSteps) {

		// We're done, so clear.

		clearInterval(fadeTimer[theID]);
		fadeActive[theID] = false;
		fadeTimer[theID] = false;

		// Should we close it once the fade is complete?

		if (fadeClose[theID] == true) {
			document.getElementById(theID).style.visibility = "hidden";
		}

		// Hang on.. did a command queue while we were working? If so, make it happen now

		if (fadeQueue[theID] && fadeQueue[theID] != false) {
			fadeElementSetup(fadeQueue[theID][0], fadeQueue[theID][1], fadeQueue[theID][2], fadeQueue[theID][3]);
			fadeQueue[theID] = false;
		}
	} else {

		fadeCurrent++;
		
		// Now actually do the fade adjustment.
		
		if (fadeMode[theID] == "shadow") {

			// Do a special fade on the webkit-box-shadow of the object
		
			if (fadeAmount < 0) {
				document.getElementById(theID).style.webkitBoxShadow = shadowSettings + (Math.abs(fadeCurrent * fadeAmount)) + ')';
			} else {
				document.getElementById(theID).style.webkitBoxShadow = shadowSettings + (100 - (fadeCurrent * fadeAmount)) + ')';
			}
			
		} else {
		
			// Set the opacity depending on if we're adding or subtracting (pos or neg)
			
			if (fadeAmount < 0) {
				setOpacity(Math.abs(fadeCurrent * fadeAmount), theID);
			} else {
				setOpacity(100 - (fadeCurrent * fadeAmount), theID);
			}
		}

		// Keep going, and send myself the updated variables
		clearInterval(fadeTimer[theID]);
		fadeTimer[theID] = setInterval("fadeElement('"+theID+"', '"+fadeCurrent+"', '"+fadeAmount+"', '"+fadeSteps+"')", 15);
	}
}

////////////////////////////
//
// UTILITY functions
//

// Utility: Set the opacity, compatible with a number of browsers. Value from 0 to 100.

function setOpacity(opacity, theID) {

	var object = document.getElementById(theID).style;

	// If it's 100, set it to 99 for Firefox.

	if (navigator.userAgent.indexOf("Firefox") != -1) {
		if (opacity == 100) { opacity = 99.9999; } // This is majorly awkward
	}

	// Multi-browser opacity setting

	object.filter = "alpha(opacity=" + opacity + ")"; // IE/Win
	object.opacity = (opacity / 100);                 // Safari 1.2, Firefox+Mozilla

}

// Utility: Math functions for animation calucations - From http://www.robertpenner.com/easing/
//
// t = time, b = begin, c = change, d = duration
// time = current frame, begin is fixed, change is basically finish - begin, duration is fixed (frames),

function linear(t, b, c, d)
{
	return c*t/d + b;
}

function sineInOut(t, b, c, d)
{
	return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
}

function cubicIn(t, b, c, d) {
	return c*(t/=d)*t*t + b;
}

function cubicOut(t, b, c, d) {
	return c*((t=t/d-1)*t*t + 1) + b;
}

function cubicInOut(t, b, c, d)
{
	if ((t/=d/2) < 1) return c/2*t*t*t + b;
	return c/2*((t-=2)*t*t + 2) + b;
}

function bounceOut(t, b, c, d)
{
	if ((t/=d) < (1/2.75)){
		return c*(7.5625*t*t) + b;
	} else if (t < (2/2.75)){
		return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b;
	} else if (t < (2.5/2.75)){
		return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b;
	} else {
		return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b;
	}
}


// Utility: Get the size of the window, and set myWidth and myHeight
// Credit to quirksmode.org

function getSize() {

	// Window Size

	if (self.innerHeight) { // Everyone but IE
		myWidth = window.innerWidth;
		myHeight = window.innerHeight;
		myScroll = window.pageYOffset;
	} else if (document.documentElement && document.documentElement.clientHeight) { // IE6 Strict
		myWidth = document.documentElement.clientWidth;
		myHeight = document.documentElement.clientHeight;
		myScroll = document.documentElement.scrollTop;
	} else if (document.body) { // Other IE, such as IE7
		myWidth = document.body.clientWidth;
		myHeight = document.body.clientHeight;
		myScroll = document.body.scrollTop;
	}

	// Page size w/offscreen areas

	if (window.innerHeight && window.scrollMaxY) {	
		myScrollWidth = document.body.scrollWidth;
		myScrollHeight = window.innerHeight + window.scrollMaxY;
	} else if (document.body.scrollHeight > document.body.offsetHeight) { // All but Explorer Mac
		myScrollWidth = document.body.scrollWidth;
		myScrollHeight = document.body.scrollHeight;
	} else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari
		myScrollWidth = document.body.offsetWidth;
		myScrollHeight = document.body.offsetHeight;
	}
}

// Utility: Get Shift Key Status
// IE events don't seem to get passed through the function, so grab it from the window.

function getShift(evt) {
	var shift = false;
	if (! evt && window.event) {
		shift = window.event.shiftKey;
	} else if (evt) {
		shift = evt.shiftKey;
		if (shift) evt.stopPropagation(); // Prevents Firefox from doing shifty things
	}
	return shift;
}

// Utility: Find the Y position of an element on a page. Return Y and X as an array

function findElementPos(elemFind)
{
	var elemX = 0;
	var elemY = 0;
	do {
		elemX += elemFind.offsetLeft;
		elemY += elemFind.offsetTop;
	} while ( elemFind = elemFind.offsetParent )

	return Array(elemX, elemY);
}

var JiveViewThreads = Class.create();
JiveViewThreads.prototype = {

/*
* Initialize the JiveViewThreads object.
* @elementID The id of the thread view container
* @viewThreadsURL The url to reload thread view
*/
    initialize: function(elementID, viewThreadsURL)
    {
        this.elementID = elementID;
        this.viewThreadsURL = viewThreadsURL;
    },

    reload: function(shouldScroll) {
        var instance = this;
        new Ajax.Updater(this.elementID, this.viewThreadsURL, {
            method: 'get',
            asynchronous:true,
            evalScripts:true,
            parameters: $('jive-viewthreads-form').serialize(true),
            onComplete: function() {
                if (shouldScroll) {
                    $(instance.elementID).scrollTo();
                }
            }
        });
    },

    resetFilter: function(filter) {
        $('jiveviewthreadsform-filter').value = filter;
        this.setTagSet(-1);
    },

    setTagSet: function(tagSet) {
        if (tagSet > 0) {
            $('jiveviewthreadsform-tagset').value = tagSet;
        } else {
            $('jiveviewthreadsform-tagset').value = '';
        }
        this.setStart(0);
    },

    markAllRead: function() {
        $('jiveviewthreadsform-markAllRead').value = "true";
        this.setStart(0);
    },

    setStart: function(start) {
        $('jiveviewthreadsform-start').value = start;
        this.reload(false);
    },

    setStartAndScroll: function(start) {
        $('jiveviewthreadsform-start').value = start;
        this.reload(true);
    }
}

