// Modified by Jive Software to remove dynamic portions
/*
 * Copyright 2005 Joe Walker
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Declare an object to which we can add real functions.
 */
if (dwr == null) var dwr = {};
if (dwr.engine == null) dwr.engine = {};
if (DWREngine == null) var DWREngine = dwr.engine;

/**
 * Set an alternative error handler from the default alert box.
 * @see getahead.org/dwr/browser/engine/errors
 */
dwr.engine.setErrorHandler = function(handler) {
  dwr.engine._errorHandler = handler;
};

/**
 * Set an alternative warning handler from the default alert box.
 * @see getahead.org/dwr/browser/engine/errors
 */
dwr.engine.setWarningHandler = function(handler) {
  dwr.engine._warningHandler = handler;
};

/**
 * Setter for the text/html handler - what happens if a DWR request gets an HTML
 * reply rather than the expected Javascript. Often due to login timeout
 */
dwr.engine.setTextHtmlHandler = function(handler) {
  dwr.engine._textHtmlHandler = handler;
};

/**
 * Set a default timeout value for all calls. 0 (the default) turns timeouts off.
 * @see getahead.org/dwr/browser/engine/errors
 */
dwr.engine.setTimeout = function(timeout) {
  dwr.engine._timeout = timeout;
};

/**
 * The Pre-Hook is called before any DWR remoting is done.
 * @see getahead.org/dwr/browser/engine/hooks
 */
dwr.engine.setPreHook = function(handler) {
  dwr.engine._preHook = handler;
};

/**
 * The Post-Hook is called after any DWR remoting is done.
 * @see getahead.org/dwr/browser/engine/hooks
 */
dwr.engine.setPostHook = function(handler) {
  dwr.engine._postHook = handler;
};

/**
 * Custom headers for all DWR calls
 * @see getahead.org/dwr/????
 */
dwr.engine.setHeaders = function(headers) {
  dwr.engine._headers = headers;
};

/**
 * Custom parameters for all DWR calls
 * @see getahead.org/dwr/????
 */
dwr.engine.setParameters = function(parameters) {
  dwr.engine._parameters = parameters;
};

/** XHR remoting type constant. See dwr.engine.set[Rpc|Poll]Type() */
dwr.engine.XMLHttpRequest = 1;

/** XHR remoting type constant. See dwr.engine.set[Rpc|Poll]Type() */
dwr.engine.IFrame = 2;

/** XHR remoting type constant. See dwr.engine.setRpcType() */
dwr.engine.ScriptTag = 3;

/**
 * Set the preferred remoting type.
 * @param newType One of dwr.engine.XMLHttpRequest or dwr.engine.IFrame or dwr.engine.ScriptTag
 * @see getahead.org/dwr/browser/engine/options
 */
dwr.engine.setRpcType = function(newType) {
  if (newType != dwr.engine.XMLHttpRequest && newType != dwr.engine.IFrame && newType != dwr.engine.ScriptTag) {
    dwr.engine._handleError(null, { name:"dwr.engine.invalidRpcType", message:"RpcType must be one of dwr.engine.XMLHttpRequest or dwr.engine.IFrame or dwr.engine.ScriptTag" });
    return;
  }
  dwr.engine._rpcType = newType;
};

/**
 * Which HTTP method do we use to send results? Must be one of "GET" or "POST".
 * @see getahead.org/dwr/browser/engine/options
 */
dwr.engine.setHttpMethod = function(httpMethod) {
  if (httpMethod != "GET" && httpMethod != "POST") {
    dwr.engine._handleError(null, { name:"dwr.engine.invalidHttpMethod", message:"Remoting method must be one of GET or POST" });
    return;
  }
  dwr.engine._httpMethod = httpMethod;
};

/**
 * Ensure that remote calls happen in the order in which they were sent? (Default: false)
 * @see getahead.org/dwr/browser/engine/ordering
 */
dwr.engine.setOrdered = function(ordered) {
  dwr.engine._ordered = ordered;
};

/**
 * Do we ask the XHR object to be asynchronous? (Default: true)
 * @see getahead.org/dwr/browser/engine/options
 */
dwr.engine.setAsync = function(async) {
  dwr.engine._async = async;
};

/**
 * Does DWR poll the server for updates? (Default: false)
 * @see getahead.org/dwr/browser/engine/options
 */
dwr.engine.setActiveReverseAjax = function(activeReverseAjax) {
  if (activeReverseAjax) {
    // Bail if we are already started
    if (dwr.engine._activeReverseAjax) return;
    dwr.engine._activeReverseAjax = true;
    dwr.engine._poll();
  }
  else {
    // Can we cancel an existing request?
    if (dwr.engine._activeReverseAjax && dwr.engine._pollReq) dwr.engine._pollReq.abort();
    dwr.engine._activeReverseAjax = false;
  }
  // TODO: in iframe mode, if we start, stop, start then the second start may
  // well kick off a second iframe while the first is still about to return
  // we should cope with this but we don't
};

/**
 * The default message handler.
 * @see getahead.org/dwr/browser/engine/errors
 */
dwr.engine.defaultErrorHandler = function(message, ex) {
  dwr.engine._debug("Error: " + ex.name + ", " + ex.message, true);
  if (message == null || message == "") alert("A server error has occured.");
  // Ignore NS_ERROR_NOT_AVAILABLE if Mozilla is being narky
  else if (message.indexOf("0x80040111") != -1) dwr.engine._debug(message);
  else alert(message);
};

/**
 * The default warning handler.
 * @see getahead.org/dwr/browser/engine/errors
 */
dwr.engine.defaultWarningHandler = function(message, ex) {
  dwr.engine._debug(message);
};

/**
 * For reduced latency you can group several remote calls together using a batch.
 * @see getahead.org/dwr/browser/engine/batch
 */
dwr.engine.beginBatch = function() {
  if (dwr.engine._batch) {
    dwr.engine._handleError(null, { name:"dwr.engine.batchBegun", message:"Batch already begun" });
    return;
  }
  dwr.engine._batch = dwr.engine._createBatch();
};

/**
 * Finished grouping a set of remote calls together. Go and execute them all.
 * @see getahead.org/dwr/browser/engine/batch
 */
dwr.engine.endBatch = function(options) {
  var batch = dwr.engine._batch;
  if (batch == null) {
    dwr.engine._handleError(null, { name:"dwr.engine.batchNotBegun", message:"No batch in progress" });
    return;
  }
  dwr.engine._batch = null;
  if (batch.map.callCount == 0) return;

  // The hooks need to be merged carefully to preserve ordering
  if (options) dwr.engine._mergeBatch(batch, options);

  // In ordered mode, we don't send unless the list of sent items is empty
  if (dwr.engine._ordered && dwr.engine._batchesLength != 0) {
    dwr.engine._batchQueue[dwr.engine._batchQueue.length] = batch;
  }
  else {
    dwr.engine._sendData(batch);
  }
};

/** @deprecated */
dwr.engine.setPollMethod = function(type) { dwr.engine.setPollType(type); };
dwr.engine.setMethod = function(type) { dwr.engine.setRpcType(type); };
dwr.engine.setVerb = function(verb) { dwr.engine.setHttpMethod(verb); };
dwr.engine.setPollType = function() { dwr.engine._debug("Manually setting the Poll Type is not supported"); };

//==============================================================================
// Only private stuff below here
//==============================================================================

/** The original page id sent from the server */
// dwr.engine._origScriptSessionId = "${scriptSessionId}";

/** The session cookie name */
// dwr.engine._sessionCookieName = "${sessionCookieName}"; // JSESSIONID

/** Is GET enabled for the benefit of Safari? */
// dwr.engine._allowGetForSafariButMakeForgeryEasier = "${allowGetForSafariButMakeForgeryEasier}";

/** The script prefix to strip in the case of scriptTagProtection. */
// dwr.engine._scriptTagProtection = "${scriptTagProtection}";

/** The default path to the DWR servlet */
// dwr.engine._defaultPath = "${defaultPath}";

/** Do we use XHR for reverse ajax because we are not streaming? */
// dwr.engine._pollWithXhr = "${pollWithXhr}";

/** The read page id that we calculate */
dwr.engine._scriptSessionId = null;

/** The function that we use to fetch/calculate a session id */
dwr.engine._getScriptSessionId = function() {
  if (dwr.engine._scriptSessionId == null) {
    dwr.engine._scriptSessionId = dwr.engine._origScriptSessionId + Math.floor(Math.random() * 1000);
  }
  return dwr.engine._scriptSessionId;
};

/** A function to call if something fails. */
dwr.engine._errorHandler = dwr.engine.defaultErrorHandler;

/** For debugging when something unexplained happens. */
dwr.engine._warningHandler = dwr.engine.defaultWarningHandler;

/** A function to be called before requests are marshalled. Can be null. */
dwr.engine._preHook = null;

/** A function to be called after replies are received. Can be null. */
dwr.engine._postHook = null;

/** An map of the batches that we have sent and are awaiting a reply on. */
dwr.engine._batches = {};

/** A count of the number of outstanding batches. Should be == to _batches.length unless prototype has messed things up */
dwr.engine._batchesLength = 0;

/** In ordered mode, the array of batches waiting to be sent */
dwr.engine._batchQueue = [];

/** What is the default rpc type */
dwr.engine._rpcType = dwr.engine.XMLHttpRequest;

/** What is the default remoting method (ie GET or POST) */
dwr.engine._httpMethod = "POST";

/** Do we attempt to ensure that calls happen in the order in which they were sent? */
dwr.engine._ordered = false;

/** Do we make the calls async? */
dwr.engine._async = true;

/** The current batch (if we are in batch mode) */
dwr.engine._batch = null;

/** The global timeout */
dwr.engine._timeout = 0;

/** ActiveX objects to use when we want to convert an xml string into a DOM object. */
dwr.engine._DOMDocument = ["Msxml2.DOMDocument.6.0", "Msxml2.DOMDocument.5.0", "Msxml2.DOMDocument.4.0", "Msxml2.DOMDocument.3.0", "MSXML2.DOMDocument", "MSXML.DOMDocument", "Microsoft.XMLDOM"];

/** The ActiveX objects to use when we want to do an XMLHttpRequest call. */
dwr.engine._XMLHTTP = ["Msxml2.XMLHTTP.6.0", "Msxml2.XMLHTTP.5.0", "Msxml2.XMLHTTP.4.0", "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP"];

/** Are we doing comet or polling? */
dwr.engine._activeReverseAjax = false;

/** The iframe that we are using to poll */
dwr.engine._outstandingIFrames = [];

/** The xhr object that we are using to poll */
dwr.engine._pollReq = null;

/** How many milliseconds between internal comet polls */
dwr.engine._pollCometInterval = 200;

/** How many times have we re-tried to poll? */
dwr.engine._pollRetries = 0;
dwr.engine._maxPollRetries = 0;

/** Do we do a document.reload if we get a text/html reply? */
dwr.engine._textHtmlHandler = null;

/** If you wish to send custom headers with every request */
dwr.engine._headers = null;

/** If you wish to send extra custom request parameters with each request */
dwr.engine._parameters = null;

/** Undocumented interceptors - do not use */
dwr.engine._postSeperator = "\n";
dwr.engine._defaultInterceptor = function(data) { return data; };
dwr.engine._urlRewriteHandler = dwr.engine._defaultInterceptor;
dwr.engine._contentRewriteHandler = dwr.engine._defaultInterceptor;
dwr.engine._replyRewriteHandler = dwr.engine._defaultInterceptor;

/** Batch ids allow us to know which batch the server is answering */
dwr.engine._nextBatchId = 0;

/** A list of the properties that need merging from calls to a batch */
dwr.engine._propnames = [ "rpcType", "httpMethod", "async", "timeout", "errorHandler", "warningHandler", "textHtmlHandler" ];

/** Do we stream, or can be hacked to do so? */
dwr.engine._partialResponseNo = 0;
dwr.engine._partialResponseYes = 1;
dwr.engine._partialResponseFlush = 2;

/** Is this page in the process of unloading? */
dwr.engine._unloading = false;

/**
 * @private Send a request. Called by the Javascript interface stub
 * @param path part of URL after the host and before the exec bit without leading or trailing /s
 * @param scriptName The class to execute
 * @param methodName The method on said class to execute
 * @param func The callback function to which any returned data should be passed
 *       if this is null, any returned data will be ignored
 * @param vararg_params The parameters to pass to the above class
 */
dwr.engine._execute = function(path, scriptName, methodName, vararg_params) {
  var singleShot = false;
  if (dwr.engine._batch == null) {
    dwr.engine.beginBatch();
    singleShot = true;
  }
  var batch = dwr.engine._batch;
  // To make them easy to manipulate we copy the arguments into an args array
  var args = [];
  for (var i = 0; i < arguments.length - 3; i++) {
    args[i] = arguments[i + 3];
  }
  // All the paths MUST be to the same servlet
  if (batch.path == null) {
    batch.path = path;
  }
  else {
    if (batch.path != path) {
      dwr.engine._handleError(batch, { name:"dwr.engine.multipleServlets", message:"Can't batch requests to multiple DWR Servlets." });
      return;
    }
  }
  // From the other params, work out which is the function (or object with
  // call meta-data) and which is the call parameters
  var callData;
  var lastArg = args[args.length - 1];
  if (typeof lastArg == "function" || lastArg == null) callData = { callback:args.pop() };
  else callData = args.pop();

  // Merge from the callData into the batch
  dwr.engine._mergeBatch(batch, callData);
  batch.handlers[batch.map.callCount] = {
    exceptionHandler:callData.exceptionHandler,
    callback:callData.callback
  };

  // Copy to the map the things that need serializing
  var prefix = "c" + batch.map.callCount + "-";
  batch.map[prefix + "scriptName"] = scriptName;
  batch.map[prefix + "methodName"] = methodName;
  batch.map[prefix + "id"] = batch.map.callCount;
  for (i = 0; i < args.length; i++) {
    dwr.engine._serializeAll(batch, [], args[i], prefix + "param" + i);
  }

  // Now we have finished remembering the call, we incr the call count
  batch.map.callCount++;
  if (singleShot) dwr.engine.endBatch();
};

/** @private Poll the server to see if there is any data waiting */
dwr.engine._poll = function() {
  if (!dwr.engine._activeReverseAjax) return;

  var batch = dwr.engine._createBatch();
  batch.map.id = 0; // TODO: Do we need this??
  batch.map.callCount = 1;
  batch.isPoll = true;
  if (dwr.engine._pollWithXhr == "true") {
    batch.rpcType = dwr.engine.XMLHttpRequest;
    batch.map.partialResponse = dwr.engine._partialResponseNo;
  }
  else {
    if (navigator.userAgent.indexOf("Gecko/") != -1) {
      batch.rpcType = dwr.engine.XMLHttpRequest;
      batch.map.partialResponse = dwr.engine._partialResponseYes;
    }
    else {
      batch.rpcType = dwr.engine.XMLHttpRequest;
      batch.map.partialResponse = dwr.engine._partialResponseNo;
    }
  }
  batch.httpMethod = "POST";
  batch.async = true;
  batch.timeout = 0;
  batch.path = dwr.engine._defaultPath;
  batch.preHooks = [];
  batch.postHooks = [];
  batch.errorHandler = dwr.engine._pollErrorHandler;
  batch.warningHandler = dwr.engine._pollErrorHandler;
  batch.handlers[0] = {
    callback:function(pause) {
      dwr.engine._pollRetries = 0;
      setTimeout(dwr.engine._poll, pause);
    }
  };

  // Send the data
  dwr.engine._sendData(batch);
  if (batch.rpcType == dwr.engine.XMLHttpRequest && batch.map.partialResponse == dwr.engine._partialResponseYes) {
    dwr.engine._checkCometPoll();
  }
};

/** Try to recover from polling errors */
dwr.engine._pollErrorHandler = function(msg, ex) {
  // if anything goes wrong then just silently try again (up to 3x) after 10s
  dwr.engine._pollRetries++;
  dwr.engine._debug("Reverse Ajax poll failed (pollRetries=" + dwr.engine._pollRetries + "): " + ex.name + " : " + ex.message);
  if (dwr.engine._pollRetries < dwr.engine._maxPollRetries) {
    setTimeout(dwr.engine._poll, 10000);
  }
  else {
    dwr.engine._activeReverseAjax = false;
    dwr.engine._debug("Giving up.");
  }
};

/** @private Generate a new standard batch */
dwr.engine._createBatch = function() {
  var batch = {
    map:{
      callCount:0,
      page:window.location.pathname + window.location.search,
      httpSessionId:dwr.engine._getJSessionId(),
      scriptSessionId:dwr.engine._getScriptSessionId()
    },
    charsProcessed:0, paramCount:0,
    parameters:{}, headers:{},
    isPoll:false, handlers:{}, preHooks:[], postHooks:[],
    rpcType:dwr.engine._rpcType,
    httpMethod:dwr.engine._httpMethod,
    async:dwr.engine._async,
    timeout:dwr.engine._timeout,
    errorHandler:dwr.engine._errorHandler,
    warningHandler:dwr.engine._warningHandler,
    textHtmlHandler:dwr.engine._textHtmlHandler
  };
  if (dwr.engine._preHook) batch.preHooks.push(dwr.engine._preHook);
  if (dwr.engine._postHook) batch.postHooks.push(dwr.engine._postHook);
  var propname, data;
  if (dwr.engine._headers) {
    for (propname in dwr.engine._headers) {
      data = dwr.engine._headers[propname];
      if (typeof data != "function") batch.headers[propname] = data;
    }
  }
  if (dwr.engine._parameters) {
    for (propname in dwr.engine._parameters) {
      data = dwr.engine._parameters[propname];
      if (typeof data != "function") batch.parameters[propname] = data;
    }
  }
  return batch;
};

/** @private Take further options and merge them into */
dwr.engine._mergeBatch = function(batch, overrides) {
  var propname, data;
  for (var i = 0; i < dwr.engine._propnames.length; i++) {
    propname = dwr.engine._propnames[i];
    if (overrides[propname] != null) batch[propname] = overrides[propname];
  }
  if (overrides.preHook != null) batch.preHooks.unshift(overrides.preHook);
  if (overrides.postHook != null) batch.postHooks.push(overrides.postHook);
  if (overrides.headers) {
    for (propname in overrides.headers) {
      data = overrides.headers[propname];
      if (typeof data != "function") batch.headers[propname] = data;
    }
  }
  if (overrides.parameters) {
    for (propname in overrides.parameters) {
      data = overrides.parameters[propname];
      if (typeof data != "function") batch.map["p-" + propname] = "" + data;
    }
  }
};

/** @private What is our session id? */
dwr.engine._getJSessionId =  function() {
  var cookies = document.cookie.split(';');
  for (var i = 0; i < cookies.length; i++) {
    var cookie = cookies[i];
    while (cookie.charAt(0) == ' ') cookie = cookie.substring(1, cookie.length);
    if (cookie.indexOf(dwr.engine._sessionCookieName + "=") == 0) {
      return cookie.substring(dwr.engine._sessionCookieName.length + 1, cookie.length);
    }
  }
  return "";
};

/** @private Check for reverse Ajax activity */
dwr.engine._checkCometPoll = function() {
  for (var i = 0; i < dwr.engine._outstandingIFrames.length; i++) {
    var text = "";
    var iframe = dwr.engine._outstandingIFrames[i];
    try {
      text = dwr.engine._getTextFromCometIFrame(iframe);
    }
    catch (ex) {
      dwr.engine._handleWarning(iframe.batch, ex);
    }
    if (text != "") dwr.engine._processCometResponse(text, iframe.batch);
  }
  if (dwr.engine._pollReq) {
    var req = dwr.engine._pollReq;
    var text = req.responseText;
    if (text != null) dwr.engine._processCometResponse(text, req.batch);
  }

  // If the poll resources are still there, come back again
  if (dwr.engine._outstandingIFrames.length > 0 || dwr.engine._pollReq) {
    setTimeout(dwr.engine._checkCometPoll, dwr.engine._pollCometInterval);
  }
};

/** @private Extract the whole (executed an all) text from the current iframe */
dwr.engine._getTextFromCometIFrame = function(frameEle) {
  var body = frameEle.contentWindow.document.body;
  if (body == null) return "";
  var text = body.innerHTML;
  // We need to prevent IE from stripping line feeds
  if (text.indexOf("<PRE>") == 0 || text.indexOf("<pre>") == 0) {
    text = text.substring(5, text.length - 7);
  }
  return text;
};

/** @private Some more text might have come in, test and execute the new stuff */
dwr.engine._processCometResponse = function(response, batch) {
  if (batch.charsProcessed == response.length) return;
  if (response.length == 0) {
    batch.charsProcessed = 0;
    return;
  }

  var firstStartTag = response.indexOf("//#DWR-START#", batch.charsProcessed);
  if (firstStartTag == -1) {
    // dwr.engine._debug("No start tag (search from " + batch.charsProcessed + "). skipping '" + response.substring(batch.charsProcessed) + "'");
    batch.charsProcessed = response.length;
    return;
  }
  // if (firstStartTag > 0) {
  //   dwr.engine._debug("Start tag not at start (search from " + batch.charsProcessed + "). skipping '" + response.substring(batch.charsProcessed, firstStartTag) + "'");
  // }

  var lastEndTag = response.lastIndexOf("//#DWR-END#");
  if (lastEndTag == -1) {
    // dwr.engine._debug("No end tag. unchanged charsProcessed=" + batch.charsProcessed);
    return;
  }

  // Skip the end tag too for next time, remembering CR and LF
  if (response.charCodeAt(lastEndTag + 11) == 13 && response.charCodeAt(lastEndTag + 12) == 10) {
    batch.charsProcessed = lastEndTag + 13;
  }
  else {
    batch.charsProcessed = lastEndTag + 11;
  }

  var exec = response.substring(firstStartTag + 13, lastEndTag);

  dwr.engine._receivedBatch = batch;
  dwr.engine._eval(exec);
  dwr.engine._receivedBatch = null;
};

/** @private Actually send the block of data in the batch object. */
dwr.engine._sendData = function(batch) {
  batch.map.batchId = dwr.engine._nextBatchId;
  dwr.engine._nextBatchId++;
  dwr.engine._batches[batch.map.batchId] = batch;
  dwr.engine._batchesLength++;
  batch.completed = false;

  for (var i = 0; i < batch.preHooks.length; i++) {
    batch.preHooks[i]();
  }
  batch.preHooks = null;
  // Set a timeout
  if (batch.timeout && batch.timeout != 0) {
    batch.timeoutId = setTimeout(function() { dwr.engine._abortRequest(batch); }, batch.timeout);
  }
  // Get setup for XMLHttpRequest if possible
  if (batch.rpcType == dwr.engine.XMLHttpRequest) {
    if (window.XMLHttpRequest) {
      batch.req = new XMLHttpRequest();
    }
    // IE5 for the mac claims to support window.ActiveXObject, but throws an error when it's used
    else if (window.ActiveXObject && !(navigator.userAgent.indexOf("Mac") >= 0 && navigator.userAgent.indexOf("MSIE") >= 0)) {
      batch.req = dwr.engine._newActiveXObject(dwr.engine._XMLHTTP);
    }
  }

  var prop, request;
  if (batch.req) {
    // Proceed using XMLHttpRequest
    if (batch.async) {
      batch.req.onreadystatechange = function() {
        if (typeof dwr != 'undefined') dwr.engine._stateChange(batch);
      };
    }
    // If we're polling, record this for monitoring
    if (batch.isPoll) {
      dwr.engine._pollReq = batch.req;
      // In IE XHR is an ActiveX control so you can't augment it like this
      if (!(document.all && !window.opera)) batch.req.batch = batch;
    }
    // Workaround for Safari 1.x POST bug
    var indexSafari = navigator.userAgent.indexOf("Safari/");
    if (indexSafari >= 0) {
      var version = navigator.userAgent.substring(indexSafari + 7);
      if (parseInt(version, 10) < 400) {
        if (dwr.engine._allowGetForSafariButMakeForgeryEasier == "true") batch.httpMethod = "GET";
        else dwr.engine._handleWarning(batch, { name:"dwr.engine.oldSafari", message:"Safari GET support disabled. See getahead.org/dwr/server/servlet and allowGetForSafariButMakeForgeryEasier." });
      }
    }
    batch.mode = batch.isPoll ? dwr.engine._ModePlainPoll : dwr.engine._ModePlainCall;
    request = dwr.engine._constructRequest(batch);
    try {
      batch.req.open(batch.httpMethod, request.url, batch.async);
      try {
        for (prop in batch.headers) {
          var value = batch.headers[prop];
          if (typeof value == "string") batch.req.setRequestHeader(prop, value);
        }
        if (!batch.headers["Content-Type"]) batch.req.setRequestHeader("Content-Type", "text/plain");
      }
      catch (ex) {
        dwr.engine._handleWarning(batch, ex);
      }
      batch.req.send(request.body);
      if (!batch.async) dwr.engine._stateChange(batch);
    }
    catch (ex) {
      dwr.engine._handleError(batch, ex);
    }
  }
  else if (batch.rpcType != dwr.engine.ScriptTag) {
    var idname = batch.isPoll ? "dwr-if-poll-" + batch.map.batchId : "dwr-if-" + batch.map.batchId;
    // Removed htmlfile implementation. Don't expect it to return before v3
    batch.div = document.createElement("div");
    // Add the div to the document first, otherwise IE 6 will ignore onload handler.
    document.body.appendChild(batch.div);
    batch.div.innerHTML = "<iframe src='javascript:void(0)' frameborder='0' style='width:0px;height:0px;border:0;' id='" + idname + "' name='" + idname + "' onload='dwr.engine._iframeLoadingComplete (" + batch.map.batchId + ");'></iframe>";
    batch.document = document;
    batch.iframe = batch.document.getElementById(idname);
    batch.iframe.batch = batch;
    batch.mode = batch.isPoll ? dwr.engine._ModeHtmlPoll : dwr.engine._ModeHtmlCall;
    if (batch.isPoll) dwr.engine._outstandingIFrames.push(batch.iframe);
    request = dwr.engine._constructRequest(batch);
    if (batch.httpMethod == "GET") {
      batch.iframe.setAttribute("src", request.url);
    }
    else {
      batch.form = batch.document.createElement("form");
      batch.form.setAttribute("id", "dwr-form");
      batch.form.setAttribute("action", request.url);
      batch.form.setAttribute("style", "display:none;");
      batch.form.setAttribute("target", idname);
      batch.form.target = idname;
      batch.form.setAttribute("method", batch.httpMethod);
      for (prop in batch.map) {
        var value = batch.map[prop];
        if (typeof value != "function") {
          var formInput = batch.document.createElement("input");
          formInput.setAttribute("type", "hidden");
          formInput.setAttribute("name", prop);
          formInput.setAttribute("value", value);
          batch.form.appendChild(formInput);
        }
      }
      batch.document.body.appendChild(batch.form);
      batch.form.submit();
    }
  }
  else {
    batch.httpMethod = "GET"; // There's no such thing as ScriptTag using POST
    batch.mode = batch.isPoll ? dwr.engine._ModePlainPoll : dwr.engine._ModePlainCall;
    request = dwr.engine._constructRequest(batch);
    batch.script = document.createElement("script");
    batch.script.id = "dwr-st-" + batch.map["c0-id"];
    batch.script.src = request.url;
    document.body.appendChild(batch.script);
  }
};

dwr.engine._ModePlainCall = "/call/plaincall/";
dwr.engine._ModeHtmlCall = "/call/htmlcall/";
dwr.engine._ModePlainPoll = "/call/plainpoll/";
dwr.engine._ModeHtmlPoll = "/call/htmlpoll/";

/** @private Work out what the URL should look like */
dwr.engine._constructRequest = function(batch) {
  // A quick string to help people that use web log analysers
  var request = { url:batch.path + batch.mode, body:null };
  if (batch.isPoll == true) {
    request.url += "ReverseAjax.dwr";
  }
  else if (batch.map.callCount == 1) {
    request.url += batch.map["c0-scriptName"] + "." + batch.map["c0-methodName"] + ".dwr";
  }
  else {
    request.url += "Multiple." + batch.map.callCount + ".dwr";
  }
  // Play nice with url re-writing
  var sessionMatch = location.href.match(/jsessionid=([^?]+)/);
  if (sessionMatch != null) {
    request.url += ";jsessionid=" + sessionMatch[1];
  }

  var prop;
  if (batch.httpMethod == "GET") {
    // Some browsers (Opera/Safari2) seem to fail to convert the callCount value
    // to a string in the loop below so we do it manually here.
    batch.map.callCount = "" + batch.map.callCount;
    request.url += "?";
    for (prop in batch.map) {
      if (typeof batch.map[prop] != "function") {
        request.url += encodeURIComponent(prop) + "=" + encodeURIComponent(batch.map[prop]) + "&";
      }
    }
    request.url = request.url.substring(0, request.url.length - 1);
  }
  else {
    // PERFORMANCE: for iframe mode this is thrown away.
    request.body = "";
    if (document.all && !window.opera) {
      // Use array joining on IE (fastest)
      var buf = [];
      for (prop in batch.map) {
        if (typeof batch.map[prop] != "function") {
          buf.push(prop + "=" + batch.map[prop] + dwr.engine._postSeperator);
        }
      }
      request.body = buf.join("");
    }
    else {
      // Use string concat on other browsers (fastest)
      for (prop in batch.map) {
        if (typeof batch.map[prop] != "function") {
          request.body += prop + "=" + batch.map[prop] + dwr.engine._postSeperator;
        }
      }
    }
    request.body = dwr.engine._contentRewriteHandler(request.body);
  }
  request.url = dwr.engine._urlRewriteHandler(request.url);
  return request;
};

/** @private Called by XMLHttpRequest to indicate that something has happened */
dwr.engine._stateChange = function(batch) {
  var toEval;

  if (batch.completed) {
    dwr.engine._debug("Error: _stateChange() with batch.completed");
    return;
  }

  var req = batch.req;
  try {
    if (req.readyState != 4) return;
  }
  catch (ex) {
    dwr.engine._handleWarning(batch, ex);
    // It's broken - clear up and forget this call
    dwr.engine._clearUp(batch);
    return;
  }

  if (dwr.engine._unloading) {
    dwr.engine._debug("Ignoring reply from server as page is unloading.");
    return;
  }

  try {
    var reply = req.responseText;
    reply = dwr.engine._replyRewriteHandler(reply);
    var status = req.status; // causes Mozilla to except on page moves

    if (reply == null || reply == "") {
      dwr.engine._handleWarning(batch, { name:"dwr.engine.missingData", message:"No data received from server" });
    }
    else if (status != 200) {
      dwr.engine._handleError(batch, { name:"dwr.engine.http." + status, message:req.statusText });
    }
    else {
      var contentType = req.getResponseHeader("Content-Type");
      if (!contentType.match(/^text\/plain/) && !contentType.match(/^text\/javascript/)) {
        if (contentType.match(/^text\/html/) && typeof batch.textHtmlHandler == "function") {
          batch.textHtmlHandler({ status:status, responseText:reply, contentType:contentType });
        }
        else {
          dwr.engine._handleWarning(batch, { name:"dwr.engine.invalidMimeType", message:"Invalid content type: '" + contentType + "'" });
        }
      }
      else {
        // Comet replies might have already partially executed
        if (batch.isPoll && batch.map.partialResponse == dwr.engine._partialResponseYes) {
          dwr.engine._processCometResponse(reply, batch);
        }
        else {
          if (reply.search("//#DWR") == -1) {
            dwr.engine._handleWarning(batch, { name:"dwr.engine.invalidReply", message:"Invalid reply from server" });
          }
          else {
            toEval = reply;
          }
        }
      }
    }
  }
  catch (ex) {
    dwr.engine._handleWarning(batch, ex);
  }

  dwr.engine._callPostHooks(batch);

  // Outside of the try/catch so errors propogate normally:
  dwr.engine._receivedBatch = batch;
  if (toEval != null) toEval = toEval.replace(dwr.engine._scriptTagProtection, "");
  dwr.engine._eval(toEval);
  dwr.engine._receivedBatch = null;
  dwr.engine._validateBatch(batch);
  if (!batch.completed) dwr.engine._clearUp(batch);
};

/**
 * @private This function is invoked when a batch reply is received.
 * It checks that there is a response for every call in the batch. Otherwise,
 * an error will be signaled (a call without a response indicates that the
 * server failed to send complete batch response).
 */
dwr.engine._validateBatch = function(batch) {
  // If some call left unreplied, report an error.
  if (!batch.completed) {
    for (var i = 0; i < batch.map.callCount; i++) {
      if (batch.handlers[i] != null) {
        dwr.engine._handleWarning(batch, { name:"dwr.engine.incompleteReply", message:"Incomplete reply from server" });
        break;
      }
    }
  }
}

/** @private Called from iframe onload, check batch using batch-id */
dwr.engine._iframeLoadingComplete = function(batchId) {
  // dwr.engine._checkCometPoll();
  var batch = dwr.engine._batches[batchId];
  if (batch) dwr.engine._validateBatch(batch);
}

/** @private Called by the server: Execute a callback */
dwr.engine._remoteHandleCallback = function(batchId, callId, reply) {
  var batch = dwr.engine._batches[batchId];
  if (batch == null) {
    dwr.engine._debug("Warning: batch == null in remoteHandleCallback for batchId=" + batchId, true);
    return;
  }
  // Error handlers inside here indicate an error that is nothing to do
  // with DWR so we handle them differently.
  try {
    var handlers = batch.handlers[callId];
    batch.handlers[callId] = null;
    if (!handlers) {
      dwr.engine._debug("Warning: Missing handlers. callId=" + callId, true);
    }
    else if (typeof handlers.callback == "function") handlers.callback(reply);
  }
  catch (ex) {
    dwr.engine._handleError(batch, ex);
  }
};

/** @private Called by the server: Handle an exception for a call */
dwr.engine._remoteHandleException = function(batchId, callId, ex) {
  var batch = dwr.engine._batches[batchId];
  if (batch == null) { dwr.engine._debug("Warning: null batch in remoteHandleException", true); return; }
  var handlers = batch.handlers[callId];
  batch.handlers[callId] = null;
  if (handlers == null) { dwr.engine._debug("Warning: null handlers in remoteHandleException", true); return; }
  if (ex.message == undefined) ex.message = "";
  if (typeof handlers.exceptionHandler == "function") handlers.exceptionHandler(ex.message, ex);
  else if (typeof batch.errorHandler == "function") batch.errorHandler(ex.message, ex);
};

/** @private Called by the server: The whole batch is broken */
dwr.engine._remoteHandleBatchException = function(ex, batchId) {
  var searchBatch = (dwr.engine._receivedBatch == null && batchId != null);
  if (searchBatch) {
    dwr.engine._receivedBatch = dwr.engine._batches[batchId];
  }
  if (ex.message == undefined) ex.message = "";
  dwr.engine._handleError(dwr.engine._receivedBatch, ex);
  if (searchBatch) {
    dwr.engine._receivedBatch = null;
    dwr.engine._clearUp(dwr.engine._batches[batchId]);
  }
};

/** @private Called by the server: Reverse ajax should not be used */
dwr.engine._remotePollCometDisabled = function(ex, batchId) {
  dwr.engine.setActiveReverseAjax(false);
  var searchBatch = (dwr.engine._receivedBatch == null && batchId != null);
  if (searchBatch) {
    dwr.engine._receivedBatch = dwr.engine._batches[batchId];
  }
  if (ex.message == undefined) ex.message = "";
  dwr.engine._handleError(dwr.engine._receivedBatch, ex);
  if (searchBatch) {
    dwr.engine._receivedBatch = null;
    dwr.engine._clearUp(dwr.engine._batches[batchId]);
  }
};

/** @private Called by the server: An IFrame reply is about to start */
dwr.engine._remoteBeginIFrameResponse = function(iframe, batchId) {
  if (iframe != null) dwr.engine._receivedBatch = iframe.batch;
  dwr.engine._callPostHooks(dwr.engine._receivedBatch);
};

/** @private Called by the server: An IFrame reply is just completing */
dwr.engine._remoteEndIFrameResponse = function(batchId) {
  dwr.engine._clearUp(dwr.engine._receivedBatch);
  dwr.engine._receivedBatch = null;
};

/** @private This is a hack to make the context be this window */
dwr.engine._eval = function(script) {
  if (script == null) return null;
  if (script == "") { dwr.engine._debug("Warning: blank script", true); return null; }
  // dwr.engine._debug("Exec: [" + script + "]", true);
  return eval(script);
};

/** @private Called as a result of a request timeout */
dwr.engine._abortRequest = function(batch) {
  if (batch && !batch.completed) {
    dwr.engine._clearUp(batch);
    if (batch.req) batch.req.abort();
    dwr.engine._handleError(batch, { name:"dwr.engine.timeout", message:"Timeout" });
  }
};

/** @private call all the post hooks for a batch */
dwr.engine._callPostHooks = function(batch) {
  if (batch.postHooks) {
    for (var i = 0; i < batch.postHooks.length; i++) {
      batch.postHooks[i]();
    }
    batch.postHooks = null;
  }
};

/** @private A call has finished by whatever means and we need to shut it all down. */
dwr.engine._clearUp = function(batch) {
  if (!batch) { dwr.engine._debug("Warning: null batch in dwr.engine._clearUp()", true); return; }
  if (batch.completed) { dwr.engine._debug("Warning: Double complete", true); return; }

  // IFrame tidyup
  if (batch.div) batch.div.parentNode.removeChild(batch.div);
  if (batch.iframe) {
    // If this is a poll frame then stop comet polling
    for (var i = 0; i < dwr.engine._outstandingIFrames.length; i++) {
      if (dwr.engine._outstandingIFrames[i] == batch.iframe) {
        dwr.engine._outstandingIFrames.splice(i, 1);
      }
    }
    batch.iframe.parentNode.removeChild(batch.iframe);
  }
  if (batch.form) batch.form.parentNode.removeChild(batch.form);

  // XHR tidyup: avoid IE handles increase
  if (batch.req) {
    // If this is a poll frame then stop comet polling
    if (batch.req == dwr.engine._pollReq) dwr.engine._pollReq = null;
    delete batch.req;
  }

  // Timeout tidyup
  if (batch.timeoutId) {
    clearTimeout(batch.timeoutId);
    delete batch.timeoutId;
  }

  if (batch.map && (batch.map.batchId || batch.map.batchId == 0)) {
    delete dwr.engine._batches[batch.map.batchId];
    dwr.engine._batchesLength--;
  }

  batch.completed = true;

  // If there is anything on the queue waiting to go out, then send it.
  // We don't need to check for ordered mode, here because when ordered mode
  // gets turned off, we still process *waiting* batches in an ordered way.
  if (dwr.engine._batchQueue.length != 0) {
    var sendbatch = dwr.engine._batchQueue.shift();
    dwr.engine._sendData(sendbatch);
  }
};

/** @private Abort any XHRs in progress at page unload (solves zombie socket problems in IE). */
dwr.engine._unloader = function() {
  dwr.engine._unloading = true;

  // Empty queue of waiting ordered requests
  dwr.engine._batchQueue.length = 0;

  // Abort any ongoing XHRs and clear their batches
  for (var batchId in dwr.engine._batches) {
    var batch = dwr.engine._batches[batchId];
    // Only process objects that look like batches (avoid prototype additions!)
    if (batch && batch.map) {
      if (batch.req) {
        batch.req.abort();
      }
      dwr.engine._clearUp(batch);
    }
  }
};
// Now register the unload handler
if (window.addEventListener) window.addEventListener('unload', dwr.engine._unloader, false);
else if (window.attachEvent) window.attachEvent('onunload', dwr.engine._unloader);

/** @private Generic error handling routing to save having null checks everywhere */
dwr.engine._handleError = function(batch, ex) {
  if (typeof ex == "string") ex = { name:"unknown", message:ex };
  if (ex.message == null) ex.message = "";
  if (ex.name == null) ex.name = "unknown";
  if (batch && typeof batch.errorHandler == "function") batch.errorHandler(ex.message, ex);
  else if (dwr.engine._errorHandler) dwr.engine._errorHandler(ex.message, ex);
  if (batch) dwr.engine._clearUp(batch);
};

/** @private Generic error handling routing to save having null checks everywhere */
dwr.engine._handleWarning = function(batch, ex) {
  if (typeof ex == "string") ex = { name:"unknown", message:ex };
  if (ex.message == null) ex.message = "";
  if (ex.name == null) ex.name = "unknown";
  if (batch && typeof batch.warningHandler == "function") batch.warningHandler(ex.message, ex);
  else if (dwr.engine._warningHandler) dwr.engine._warningHandler(ex.message, ex);
  if (batch) dwr.engine._clearUp(batch);
};

/**
 * @private Marshall a data item
 * @param batch A map of variables to how they have been marshalled
 * @param referto An array of already marshalled variables to prevent recurrsion
 * @param data The data to be marshalled
 * @param name The name of the data being marshalled
 */
dwr.engine._serializeAll = function(batch, referto, data, name) {
  if (data == null) {
    batch.map[name] = "null:null";
    return;
  }

  switch (typeof data) {
  case "boolean":
    batch.map[name] = "boolean:" + data;
    break;
  case "number":
    batch.map[name] = "number:" + data;
    break;
  case "string":
    batch.map[name] = "string:" + encodeURIComponent(data);
    break;
  case "object":
    if (data instanceof String) batch.map[name] = "String:" + encodeURIComponent(data);
    else if (data instanceof Boolean) batch.map[name] = "Boolean:" + data;
    else if (data instanceof Number) batch.map[name] = "Number:" + data;
    else if (data instanceof Date) batch.map[name] = "Date:" + data.getTime();
    else if (data && data.join) batch.map[name] = dwr.engine._serializeArray(batch, referto, data, name);
    else batch.map[name] = dwr.engine._serializeObject(batch, referto, data, name);
    break;
  case "function":
    // We just ignore functions.
    break;
  default:
    dwr.engine._handleWarning(null, { name:"dwr.engine.unexpectedType", message:"Unexpected type: " + typeof data + ", attempting default converter." });
    batch.map[name] = "default:" + data;
    break;
  }
};

/** @private Have we already converted this object? */
dwr.engine._lookup = function(referto, data, name) {
  var lookup;
  // Can't use a map: getahead.org/ajax/javascript-gotchas
  for (var i = 0; i < referto.length; i++) {
    if (referto[i].data == data) {
      lookup = referto[i];
      break;
    }
  }
  if (lookup) return "reference:" + lookup.name;
  referto.push({ data:data, name:name });
  return null;
};

/** @private Marshall an object */
dwr.engine._serializeObject = function(batch, referto, data, name) {
  var ref = dwr.engine._lookup(referto, data, name);
  if (ref) return ref;

  // This check for an HTML is not complete, but is there a better way?
  // Maybe we should add: data.hasChildNodes typeof "function" == true
  if (data.nodeName && data.nodeType) {
    return dwr.engine._serializeXml(batch, referto, data, name);
  }

  // treat objects as an associative arrays
  var reply = "Object_" + dwr.engine._getObjectClassName(data) + ":{";
  var element;
  for (element in data) {
    if (typeof data[element] != "function") {
      batch.paramCount++;
      var childName = "c" + dwr.engine._batch.map.callCount + "-e" + batch.paramCount;
      dwr.engine._serializeAll(batch, referto, data[element], childName);

      reply += encodeURIComponent(element) + ":reference:" + childName + ", ";
    }
  }

  if (reply.substring(reply.length - 2) == ", ") {
    reply = reply.substring(0, reply.length - 2);
  }
  reply += "}";

  return reply;
};

/** @private Returns the classname of supplied argument obj */
dwr.engine._errorClasses = { "Error":Error, "EvalError":EvalError, "RangeError":RangeError, "ReferenceError":ReferenceError, "SyntaxError":SyntaxError, "TypeError":TypeError, "URIError":URIError };
dwr.engine._getObjectClassName = function(obj) {
  // Try to find the classname by stringifying the object's constructor
  // and extract <class> from "function <class>".
  if (obj && obj.constructor && obj.constructor.toString)
  {
    var str = obj.constructor.toString();
    var regexpmatch = str.match(/function\s+(\w+)/);
    if (regexpmatch && regexpmatch.length == 2) {
      return regexpmatch[1];
    }
  }

  // Now manually test against the core Error classes, as these in some
  // browsers successfully match to the wrong class in the
  // Object.toString() test we will do later
  if (obj && obj.constructor) {
	for (var errorname in dwr.engine._errorClasses) {
      if (obj.constructor == dwr.engine._errorClasses[errorname]) return errorname;
    }
  }

  // Try to find the classname by calling Object.toString() on the object
  // and extracting <class> from "[object <class>]"
  if (obj) {
    var str = Object.prototype.toString.call(obj);
    var regexpmatch = str.match(/\[object\s+(\w+)/);
    if (regexpmatch && regexpmatch.length==2) {
      return regexpmatch[1];
    }
  }

  // Supplied argument was probably not an object, but what is better?
  return "Object";
};

/** @private Marshall an object */
dwr.engine._serializeXml = function(batch, referto, data, name) {
  var ref = dwr.engine._lookup(referto, data, name);
  if (ref) return ref;

  var output;
  if (window.XMLSerializer) output = new XMLSerializer().serializeToString(data);
  else if (data.toXml) output = data.toXml;
  else output = data.innerHTML;

  return "XML:" + encodeURIComponent(output);
};

/** @private Marshall an array */
dwr.engine._serializeArray = function(batch, referto, data, name) {
  var ref = dwr.engine._lookup(referto, data, name);
  if (ref) return ref;

  if (document.all && !window.opera) {
    // Use array joining on IE (fastest)
    var buf = ["Array:["];
    for (var i = 0; i < data.length; i++) {
      if (i != 0) buf.push(",");
      batch.paramCount++;
      var childName = "c" + dwr.engine._batch.map.callCount + "-e" + batch.paramCount;
      dwr.engine._serializeAll(batch, referto, data[i], childName);
      buf.push("reference:");
      buf.push(childName);
    }
    buf.push("]");
    reply = buf.join("");
  }
  else {
    // Use string concat on other browsers (fastest)
    var reply = "Array:[";
    for (var i = 0; i < data.length; i++) {
      if (i != 0) reply += ",";
      batch.paramCount++;
      var childName = "c" + dwr.engine._batch.map.callCount + "-e" + batch.paramCount;
      dwr.engine._serializeAll(batch, referto, data[i], childName);
      reply += "reference:";
      reply += childName;
    }
    reply += "]";
  }

  return reply;
};

/** @private Convert an XML string into a DOM object. */
dwr.engine._unserializeDocument = function(xml) {
  var dom;
  if (window.DOMParser) {
    var parser = new DOMParser();
    dom = parser.parseFromString(xml, "text/xml");
    if (!dom.documentElement || dom.documentElement.tagName == "parsererror") {
      var message = dom.documentElement.firstChild.data;
      message += "\n" + dom.documentElement.firstChild.nextSibling.firstChild.data;
      throw message;
    }
    return dom;
  }
  else if (window.ActiveXObject) {
    dom = dwr.engine._newActiveXObject(dwr.engine._DOMDocument);
    dom.loadXML(xml); // What happens on parse fail with IE?
    return dom;
  }
  else {
    var div = document.createElement("div");
    div.innerHTML = xml;
    return div;
  }
};

/** @param axarray An array of strings to attempt to create ActiveX objects from */
dwr.engine._newActiveXObject = function(axarray) {
  var returnValue;
  for (var i = 0; i < axarray.length; i++) {
    try {
      returnValue = new ActiveXObject(axarray[i]);
      break;
    }
    catch (ex) { /* ignore */ }
  }
  return returnValue;
};

/** @private Used internally when some message needs to get to the programmer */
dwr.engine._debug = function(message, stacktrace) {
  var written = false;
  try {
    if (window.console) {
      if (stacktrace && window.console.trace) window.console.trace();
      window.console.log(message);
      written = true;
    }
    else if (window.opera && window.opera.postError) {
      window.opera.postError(message);
      written = true;
    }
  }
  catch (ex) { /* ignore */ }

  if (!written) {
    var debug = document.getElementById("dwr-debug");
    if (debug) {
      var contents = message + "<br/>" + debug.innerHTML;
      if (contents.length > 2048) contents = contents.substring(0, 2048);
      debug.innerHTML = contents;
    }
  }
};


/*

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", "javascript:false;");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,html";
        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.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.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");
        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";
        }
        return ret;
    }else if(id==0){
        return {
        non_safari_css : "a{\nborder: 1px solid transparent;\n}\nspan.jive_macro.active_link, a.jive_macro.active_link, a.active_link{\nborder: 1px solid blue;\n}\nspan.jive_macro, a.jive_macro{\nborder: 1px solid transparent;\n}",
        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_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
        remove_script_host : false,
        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(){
	}
}





/* Modifed by BL to namespace the functions */

/*

DISCLAIMER: THESE JAVASCRIPT FUNCTIONS ARE SUPPLIED 'AS IS', WITH 
NO WARRANTY EXPRESSED OR IMPLIED. YOU USE THEM AT YOUR OWN RISK. 
NEITHER PAUL STEPHENS NOR PC PLUS MAGAZINE ACCEPTS ANY LIABILITY FOR 
ANY LOSS OR DAMAGE RESULTING FROM THEIR USE, HOWEVER CAUSED. 

Paul Stephens' NetScape-based cookie-handling library

http://web.ukonline.co.uk/paul.stephens/index.htm

TO USE THIS LIBRARY, INSERT ITS CONTENTS IN A <script></script> BLOCK IN THE 
<HEAD> SECTION OF YOUR WEB PAGE SOURCE, BEFORE ANY OTHER JAVASCRIPT ROUTINES.

Feel free to use this code, but please leave this comment block in.

*/

var Cookies = {

    setCookie: function(name, value, lifespan, access_path) {
          
        var cookietext = name + "=" + escape(value)  
        if (lifespan != null) {  
            var today=new Date()     
            var expiredate = new Date()      
            expiredate.setTime(today.getTime() + 1000*60*60*24*lifespan)
            cookietext += "; expires=" + expiredate.toGMTString()
        }
        if (access_path != null) { 
            cookietext += "; PATH="+access_path 
        }
        document.cookie = cookietext 
        return null  
    },


    setDatedCookie: function(name, value, expire, access_path) {
        var cookietext = name + "=" + escape(value)
                + ((expire == null) ? "" : ("; expires=" + expire.toGMTString()))
        if (access_path != null) { 
            cookietext += "; PATH="+access_path 
        }
        document.cookie = cookietext 
        return null        
    },


    getCookie: function(Name) {
        var search = Name + "="                       
        var CookieString = document.cookie            
        var result = null                               
        if (CookieString.length > 0) {                
            offset = CookieString.indexOf(search)       
            if (offset != -1) {                         
                offset += search.length                   
                end = CookieString.indexOf(";", offset)   
                if (end == -1)                            
                    end = CookieString.length               
                result = unescape(CookieString.substring(offset, end))         
                                                
            } 
        }
        return result                                
    },


    deleteCookie: function(name) {
        Cookies.setCookie(name, "Deleted", -1)
    }
    
}


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(){};

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(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);
}


// Provide a default path to dwr.engine
if (dwr == null) var dwr = {};
if (dwr.engine == null) dwr.engine = {};
if (DWREngine == null) var DWREngine = dwr.engine;

if (Watches == null) var Watches = {};
Watches._path = '/dwr';
Watches.isThreadWatched = function(p0, callback) {
  dwr.engine._execute(Watches._path, 'Watches', 'isThreadWatched', p0, callback);
}
Watches.isDocumentWatched = function(p0, callback) {
  dwr.engine._execute(Watches._path, 'Watches', 'isDocumentWatched', p0, callback);
}
Watches.isBlogWatched = function(p0, callback) {
  dwr.engine._execute(Watches._path, 'Watches', 'isBlogWatched', p0, callback);
}
Watches.isBlogPostWatched = function(p0, callback) {
  dwr.engine._execute(Watches._path, 'Watches', 'isBlogPostWatched', p0, callback);
}
Watches.isUserWatched = function(p0, callback) {
  dwr.engine._execute(Watches._path, 'Watches', 'isUserWatched', p0, callback);
}
Watches.isCommunityWatched = function(p0, callback) {
  dwr.engine._execute(Watches._path, 'Watches', 'isCommunityWatched', p0, callback);
}
Watches.isProjectWatched = function(p0, callback) {
  dwr.engine._execute(Watches._path, 'Watches', 'isProjectWatched', p0, callback);
}
Watches.isTaskWatched = function(p0, callback) {
  dwr.engine._execute(Watches._path, 'Watches', 'isTaskWatched', p0, callback);
}
Watches.isSocialGroupWatched = function(p0, callback) {
  dwr.engine._execute(Watches._path, 'Watches', 'isSocialGroupWatched', p0, callback);
}
Watches.watchThread = function(p0, callback) {
  dwr.engine._execute(Watches._path, 'Watches', 'watchThread', p0, callback);
}
Watches.watchDocument = function(p0, callback) {
  dwr.engine._execute(Watches._path, 'Watches', 'watchDocument', p0, callback);
}
Watches.watchBlog = function(p0, callback) {
  dwr.engine._execute(Watches._path, 'Watches', 'watchBlog', p0, callback);
}
Watches.watchBlogPost = function(p0, callback) {
  dwr.engine._execute(Watches._path, 'Watches', 'watchBlogPost', p0, callback);
}
Watches.watchUser = function(p0, callback) {
  dwr.engine._execute(Watches._path, 'Watches', 'watchUser', p0, callback);
}
Watches.watchCommunity = function(p0, callback) {
  dwr.engine._execute(Watches._path, 'Watches', 'watchCommunity', p0, callback);
}
Watches.watchProject = function(p0, callback) {
  dwr.engine._execute(Watches._path, 'Watches', 'watchProject', p0, callback);
}
Watches.watchTask = function(p0, callback) {
  dwr.engine._execute(Watches._path, 'Watches', 'watchTask', p0, callback);
}
Watches.watchSocialGroup = function(p0, callback) {
  dwr.engine._execute(Watches._path, 'Watches', 'watchSocialGroup', p0, callback);
}
Watches.removeThreadWatch = function(p0, callback) {
  dwr.engine._execute(Watches._path, 'Watches', 'removeThreadWatch', p0, callback);
}
Watches.removeDocumentWatch = function(p0, callback) {
  dwr.engine._execute(Watches._path, 'Watches', 'removeDocumentWatch', p0, callback);
}
Watches.removeBlogWatch = function(p0, callback) {
  dwr.engine._execute(Watches._path, 'Watches', 'removeBlogWatch', p0, callback);
}
Watches.removeBlogPostWatch = function(p0, callback) {
  dwr.engine._execute(Watches._path, 'Watches', 'removeBlogPostWatch', p0, callback);
}
Watches.removeUserWatch = function(p0, callback) {
  dwr.engine._execute(Watches._path, 'Watches', 'removeUserWatch', p0, callback);
}
Watches.removeCommunityWatch = function(p0, callback) {
  dwr.engine._execute(Watches._path, 'Watches', 'removeCommunityWatch', p0, callback);
}
Watches.removeProjectWatch = function(p0, callback) {
  dwr.engine._execute(Watches._path, 'Watches', 'removeProjectWatch', p0, callback);
}
Watches.removeTaskWatch = function(p0, callback) {
  dwr.engine._execute(Watches._path, 'Watches', 'removeTaskWatch', p0, callback);
}
Watches.removeSocialGroupWatch = function(p0, callback) {
  dwr.engine._execute(Watches._path, 'Watches', 'removeSocialGroupWatch', p0, callback);
}



// Provide a default path to dwr.engine
if (dwr == null) var dwr = {};
if (dwr.engine == null) dwr.engine = {};
if (DWREngine == null) var DWREngine = dwr.engine;

if (CommunityUtils == null) var CommunityUtils = {};
CommunityUtils._path = '/dwr';
CommunityUtils.setUserCommunityListProperty = function(p0, p1, callback) {
  dwr.engine._execute(CommunityUtils._path, 'CommunityUtils', 'setUserCommunityListProperty', p0, p1, callback);
}
CommunityUtils.closeCustomizeWidgetMessageProperty = function(callback) {
  dwr.engine._execute(CommunityUtils._path, 'CommunityUtils', 'closeCustomizeWidgetMessageProperty', callback);
}
CommunityUtils.setUserDefaultTab = function(p0, p1, callback) {
  dwr.engine._execute(CommunityUtils._path, 'CommunityUtils', 'setUserDefaultTab', p0, p1, callback);
}


