// Configuration {{{

SiteName = "myrtle-beach.vacationpeople.com";            //The full domain name of the site
SecureSiteName = "www.vacationpeople.com";            //The full domain name of the secure site

BlockPath = "adapt/blocks"     //The path to blocks file, from the root of the site ("https://" + SiteName + BlockPath should take you to the blocks folder)
BottomDate = "0000-00-00 00:00:00"; //the date that represents no value in the DB
DataSourceAddress = "adapt/data.php";
//}}}

// Declare empty variables {{{
/***********************************************************
  Hashes used to guarantee only loading 1 instance of each type
***********************************************************/
UsedScripts = {};
UsedCSS     = {};
Blocks      = {};
// }}}

// HTML helpers {{{
// SetChildNodes replaces all child nodes of the given node with nodes passed as additional functions to the call

function deleteChildNodes(node) {
  while (node.firstChild) { 
    node.removeChild(node.firstChild);
  }
}  

function SetChildNodes(node) {
  deleteChildNodes(node);
  for (var i = 1; i < arguments.length; i++) {
    node.appendChild(arguments[i]);
  }
}

// getElementByName() returns the first Element of a given name within the given node

/* TODO: getElementByName is usually called multiple times for different names within a block of code.
   It could be replaced with a getElementsByNames function that searches for all the names simultaneously
   so as to use less processor
*/
getElementByName = function (startnode, elname){
  if (startnode.getAttribute("name") == elname){
    return startnode;
  }
  for (var i = 0; i < startnode.childNodes.length; i++){
    if (startnode.childNodes[i].nodeType == 1){  //element node
      ret = getElementByName(startnode.childNodes[i], elname);
      if (ret) {
        return ret;
      }
    }
  }
  return false;
}


// ReplaceHtml is the same as setting innerHTML, but is much faster in firefox
function ReplaceHtml(el, html) {
  var newEl = el.cloneNode(false);
  // Replace the old with the new
  newEl.innerHTML = html;
  el.parentNode.replaceChild(newEl, el);
  /* Since we just removed the old element from the DOM, return a reference
  to the new element, which can be used to restore variable references. */
  return newEl;
};

// Make a tag of the given name and set its attributes from a assoc array 
//TODO: Recursion
function MakeTag(tagname, attributes){
  newtag = document.createNode(tagname);
  for (var attr in attributes){
    newtag.setAttribute(attr, attributes[attr]);
  }
}

/*****************************************************************************
* HTML Encode and Decode
*****************************************************************************/

//TODO: These are still vulnerable to script injection.  Should not be used to display input from untrusted sources

function encode(string) {
  return string.replace('&','&amp;').replace('<','&lt;').replace('>','&gt;').replace('\'','&apos;').replace('"','&quot;');
}

function unencode(string) {
  return string.replace('&amp;', '&').replace('&lt;', '<').replace('&gt;', '>').replace('&apos;', '\'').replace('&quot;', '"');
}
// }}}

// StaticReference() {{{
/********************************************************************************
*   Creates a static reference to an object and returns a string that is later eval'd
*   Current use is to reference a xmldata node from the onload function of a html node
*********************************************************************************/

Statics     = [];
function StaticReference (item) {
  Statics.push(item);
  return "Statics[" + String(Statics.length - 1) + "]";
}
// }}}

// Javascript Helpers {{{

// class.Inherits(parent) will make class a child class of parent check wiki about how to call parent's constructor
Function.prototype.Inherits = function (parent) {
  this.prototype = new parent();
  this.prototype.constructor = this;
}

// This function is not defined in Internet Explorer, so we add it here if needed
if (!Array.indexOf) { 
  Array.prototype.indexOf = function (find) {
    for (var i = 0; i < this.length; i++){
      if (this[i] == find){
        return i;
      }
    }
    return -1;
  }
}

//basic number formatting: add 0's before a number to make it the required number of digits
function ZeroPad(number, digits) { 
  str = number.toString();
  while (str.length < digits){
    str = "0" + str;
  }
  return str;
}

// Pop an alert window up
function CreatePopup(URL, width, height) {
  day = new Date();
  id = day.getTime();
  var mypage = window.open(URL, id, "'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=1,resizable=1,width=" + width + ",height=" + height + "'");
  return mypage;
}

//}}}

// Bind() {{{
/***********************************************************************************************************************************
*   Bind() takes a function and a list of parameters, and returns a function that replicates the given function, with the 
*    parameters already filled. 
*    Any parameters that are undefined in the call to Bind can be passed to the generated function.
***********************************************************************************************************************************/

block_class = function () {}

block_class.prototype.Bind = function (fn) {
  if (!fn) {
    throw new Error("Bind: attempting to bind function that doesn't exist");
  }
  var args = [];
  for (var n = 1; n < arguments.length; n++){
    args.push(arguments[n]);
  }
  var scope = this;
  return function () { 
    var myargs = [];
    var n = 0;
    while (args[n] !== undefined){
      myargs[n] = args[n];
      n += 1;
    }
    for (var i = 0; i < arguments.length; i++){
      while (args[n] !== undefined){
        myargs[n++] = args[n];
      }
      myargs[n++] = arguments[i];
    }
    return fn.apply(scope, myargs); 
  };
}
// }}}

// GetHead() and GetBody() elements {{{
/*******************************************************
*  Get the head or body element of the document
*  TODO: for an average sized document, is this faster
*  or slower than using getElementByTagName, which doesn't
*  continue searching after finding one result
********************************************************/

function GetHead(){
  heads = document.getElementsByTagName("head");
  if(!heads){
    throw new Error("GetHead(): Document has no head element");
  }
  return heads[0];
}

function GetBody(){
  bodies = document.getElementsByTagName("body");
  if(!bodies){
    throw new Error("GetBody(): Document has no body element");
  }
  return bodies[0];
}

// }}}

// DataSource class {{{
LoginClass = null;  
function DataSource (method, synchronous) {
  this.xhr = new XMLHttpRequest();
  this.method = method || "POST";
  this.asynchronous = (!synchronous);
}

DataSource.Inherits(block_class);

DataSource.prototype.SendRequest = function (url, querystring, textonly) {
  if (!querystring) { 
    querystring = "";
  }
  this.url = url; //for later reference
  this.querystring = querystring; //for later reference
  
  this.xml = undefined; //delete any old xml we had
  this.textonly = textonly;
  
  this.xhr.open(this.method, url, this.asynchronous)
  this.xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded;charset=UTF-8;");
  this.xhr.send(querystring);
  
  if (!this.asynchronous) {
    this.OnLoaded();
  }
  else {
    this.xhr.onreadystatechange = this.Bind(this.OnStateChange);
  }
};

DataSource.prototype.GetXML = function (url, querystring) {
  this.SendRequest(url, querystring, false);
}

DataSource.prototype.GetText = function (url, querystring) {
  this.SendRequest(url, querystring, true);
}

DataSource.prototype.GetProcedure = function (procedure, arguments) {
  //this has to be customized for different types of procedure calls
  var querystring = "source=" + procedure;
  for (key in arguments){
    querystring += "&" + key + "=" + escape(arguments[key]); 
  }
  this.SendRequest("http://" + SiteName + "/" + DataSourceAddress, querystring, false);
};

/*TODO: implement these
DataSource.prototype.GetService = function (service, arguments) {};

DataSource.prototype.FromObject = function (object) {};
*/

DataSource.prototype.OnStateChange = function () {
  if (this.xhr.readyState == 4){
    this.OnLoaded();
  }
}

DataSource.prototype.OnLoaded = function () {
  if (this.xhr.status==404) {
    throw new Error("Resource not found (404): " + this.url);
  }
  this.text = this.xhr.responseText;
  if (!this.textonly) { 
    this.ReadXML();
  }
  if(this.textonly || !this.ErrorCheck()) {
    if (this.onload) {
      this.onload(this);
      this.onload = undefined;
    }
  }
}

DataSource.prototype.WhenLoaded = function (onLoadHandler) {
  this.onload = Combine(this.onload, onLoadHandler);
}

DataSource.prototype.ErrorCheck = function () {
  if (this.xml.firstChild.nodeName == "error"){  //server sent back an error message
    errorid = this.xml.firstChild.getAttribute("id");
    if (errorid == "notloggedin"){
      if (LoginClass){
        LoginClass.RequireLogin();
      }
      else{
        LoginWindow = new Block("login");
        LoginWindow.WhenLoaded(this.Bind(this.CreateLoginWindow));
      }
      return true;
    }
    else if (errorid == "nosource"){
      alert("This page is attempting to access an invalid data source.");
    }
    
    throw new Error("Request Error: " + this.xhr.responseText);
    return true;
  }
  return false;
}

DataSource.prototype.CreateLoginWindow = function (block) {
  var AttachedClasses = MakePopup(block);
  LoginClass = AttachedClasses[0].value;
  LoginClass.RequireLogin();
}

DataSource.prototype.ReadXML = function () {
  if (this.xhr.responseXML && this.xhr.responseXML.firstChild) {
    this.xml = this.xhr.responseXML;
  }
  else { //it wont automatically parse unless MIME is text/xml, so force it to parse as XML -- also, IE returns empty doc on fail, so check for childnodes
    this.xml = new DOMParser().parseFromString(this.xhr.responseText, "text/xml");
    errorText = Sarissa.getParseErrorText(this.xml);
    if(errorText != Sarissa.PARSED_OK){ // The document was not loaded correctly!
      this.xml = undefined;
      throw new Error("Could not parse " + this.url + " - " + errorText);
    }
  }
}
// }}}

// Loading of Blocks {{{
/*******************************************************
  MakePage() is a callback function to be used with Blocks.
  It will make the block draw itself (and only itself) to
  the page when it loads

  MakePopup() will draw the block at the end of the current
  page.  The block must use CSS absolute positioning to 
  actually hover over the page.
********************************************************/

function MakePage(block, data) {
   var newEl = ReplaceHtml(document.body, block.Make(data));
   var newClasses = ExecuteOnloads(newEl);
   return newClasses;
}

function MakePopup(block, data) {
  var compnode = block.MakeNode(data);
  document.body.appendChild(compnode);
  return block.attachedClasses;
}

// Helper function to load a given block with a given data source 
function LoadBlock(block, procedure, arguments, onLoadHandler) {
  var checklist = new CheckList();
  var data = new DataSource();
  data.GetProcedure(procedure, arguments);
  data.WhenLoaded(checklist.check("data"));
  var block = new Block(block);
  block.WhenLoaded(checklist.check("block"));
  checklist.WhenCompleted(onLoadHandler);
}

// }}}

// Cross Browser Javascript loading {{{
/* LoadModule(): Cross-Browser loading of javascript returned by XMLHttpRequest into the global scope */

function LoadModule(code) {
  var my_global = this; // global scope reference
  if (window.execScript) {
    window.execScript(code); // eval in global scope for IE
  }
  else if (my_global.eval) {
    my_global.eval(code);
  }
  else{
    throw new Error("Browser does not support loading of JS modules");
  }
}

//}}}

// Execute Onloads() {{{
/******************************************************
  ExecuteOnloads()  HTML does not have an onload event for anything but the body node.
  However, we need to perform actions once the HTML we generate has been loaded into
  Dom nodes.  This function searches bottom up through the children of the given node,
  finding nodes with an onload property, and executing the code contained therein.

  The function returns an array of tree objects.  Each tree node has a value, which is
  the return value of the onload function that created it, and a list of children,
  which can be empty.  The children are any return values from onload functions of
  child nodes of the HTML node whose onload function generated the tree node.

********************************************************/
//TODO: The tree this function returns is not a good way to reference data.  Should return a class with methods for extracting classes by block name and original id
function ExecuteOnloads(node){
  var childClasses = Array();
  for (var i = 0; i < node.childNodes.length; i++){
    if (node.childNodes[i].nodeType == 1) { // nodeType 1 is ELEMENT_NODE
      var retval = ExecuteOnloads(node.childNodes[i]);
      childClasses = childClasses.concat(retval);
    }
  }
  var onload = node.getAttribute("onload");
  if (onload && node.nodeName.toLowerCase() != "body") {
    var tree = new Object();
    var onloadFunc = new Function("childClasses", onload);
    tree.value = onloadFunc.call(node, childClasses);
    tree.children = childClasses;
    node.removeAttribute("onload");
    return Array(tree);
  }
  return childClasses;
}
// }}}

// CheckList {{{
/**********************************************************
  The CheckList class generates a number of callback functions through
  its Check method.  Then, the function passed to CheckList.WhenCompleted()
  is called once all off the callback functions have fired.

  The function passed to WhenCompleted is called with a hash as its
  only argument.  The hash relates the itemid of each callback to an array
  of the arguments passed to the callback when it was called.

***********************************************************/

function CheckList(callback) {
  this.itemsLoading = 0;
  this.parameters = {}; 
}

CheckList.prototype.Check = function (itemid) {
  if (this.parameters[itemid]) {
    throw new Error("Each Check must have a unique id.");
  }
  this.parameters[itemid] = "[Loading]"; //Mark that we are using this id
  this.itemsLoading += 1;
  var me = this;
  return function () {
    me.parameters[itemid] = arguments;
    me.ItemLoaded();
  }
};

CheckList.prototype.ItemLoaded = function(){
  this.itemsLoading -= 1;
  this.Test();
};

CheckList.prototype.Test = function(){
  //fire our event if we have one and we aren't waiting on items
  if (this.itemsLoading == 0 && this.callback){
    this.callback();
  }
}

CheckList.prototype.WhenCompleted = function(callback) {
  this.callback = Combine(this.callback, callback);
  this.Test();
}

// }}}

// Block class {{{
/******************************************************
  Block Class - Builds a TemplateX and fills the
  generated TemplateXOutput using instructions in XML
  format
*******************************************************/

function Block(block){
  /* We only want to build each block once.  If we find we have already built it, we just call the onLoadHandler
     on the already built block.  If we have started building it, we attach our onLoadHandler to it so that it
     is fired along with the block's original onLoadHandler. */
  
  this.blocks = [];
  this.data = [];
  this.loaded = false;
  this.block = block;
  this.attachedClasses = [];
  this.arguments = [];
  
  for (var i = 1; i < arguments.length; i++){
    this.arguments.push(arguments[i]);
  }
  
  if (!Blocks[block]){
    Blocks[block] = this;
    this.GetInstructions();
  }
  else {
    Blocks[block].WhenLoaded(this.Bind(this.CopyBlock, Blocks[block]));
  }
}

Block.Inherits(block_class);

/* WhenLoaded()  If the block has loaded already, call the handler.  Otherwise call it once the block has loaded */
Block.prototype.WhenLoaded = function (onLoadHandler) {
  if (onLoadHandler){
    if (this.loaded){
      onLoadHandler(this);
    }
    else if (this.onload){
      this.onload = Combine(this.onload, onLoadHandler);
    }
    else { 
      this.onload = onLoadHandler;
    } 
  }
}

Block.prototype.CopyBlock = function(block) {
  this.blocks = block.blocks;
  this.data = block.data;
  this.template = block.template;
  this._Make = block._Make;
  this.Make = block.Make;
  this.OnLoaded();
}
    
Block.prototype.GetLocation = function (file){
  if(file.indexOf("://") > -1 ){
    return file;
  }
  return "http://" + SiteName + "/" + BlockPath + "/" + this.block + "/" + file;
}


Block.prototype.OnLoaded = function() {
  this.loaded = true;  //make sure our parent knows we are loaded
  if(this.onload) {
    this.onload(this);
    this.onload = undefined;
  }
}

Block.prototype.GetInstructions = function(url){
  var data = new DataSource();
  data.GetXML(this.GetLocation(this.block + ".xml"));
  data.WhenLoaded(this.Bind(this.GetTemplate, data));
}

Block.prototype.GetTemplate = function(instructions){
  this.instructions = instructions.xml;
  var template = this.instructions.getElementsByTagName("template");
  if (!template || !template[0].getAttribute("src")){
    throw new Error("Templating instructions must contain a <template> tag with a src attribute specifying the location of the html template.");
  }
  var data = new DataSource();
  data.GetXML(this.GetLocation(template[0].getAttribute("src")))
  data.WhenLoaded(this.Bind(this.OnTemplateLoaded));
}

Block.prototype.OnTemplateLoaded = function (data) {
  //add path to images and scripts whose src's are relative to the block folder
  var tagswithsrc = {"img" : true, "script": true};
  for (tag in tagswithsrc) {
    var foundtags = data.xml.getElementsByTagName(tag);
    for (var i = 0; i < foundtags.length; i++){
      foundtags[i].setAttribute("src", this.GetLocation(foundtags[i].getAttribute("src")));
    }
  }
  
  this.template = new TemplateX();
  this.template.CreateFromDocument(data.xml);
  this.checklist = new CheckList();
  this.PrepareTemplating(this.instructions.firstChild, this.template);
  this.CreateFunction(this.instructions.firstChild);
  this.checklist.WhenCompleted(this.Bind(this.OnLoaded)); //this will fire the onLoad if there is nothing (datasources, blocks, javascript)
}

Block.prototype.PrepareTemplating = function(node, templater){
  var me = this;
  if (node.nodeName == "class" && this.template.idVault[node.getAttribute("targetid")]){  
    templater.ExtractAttribute(node.getAttribute("targetid"), "onload");
  }

  if (node.nodeName == "make") {
    //replace our Make function with the custom make function once dependent files have been received 
    this.customMake = node.getAttribute("function");
    this.checklist.WhenCompleted(function () { me.Make = eval(me.customMake); }); //this MUST happen before block.OnLoaded
  }
  if (node.nodeName == "replace" || node.nodeName == "repeat") {
    var targetid = node.getAttribute("targetid");
    var colon = targetid.indexOf(":");
    if (colon > -1){
      var idpart = targetid.substr(0, colon);
      var attributepart = targetid.substr(colon + 1);
      templater.ExtractAttribute(idpart, attributepart);
    }
    else {
      templater = templater.Extract(targetid);
    }
  }

  if (node.getAttribute("block")){
    if (!this.blocks[node.getAttribute("block")]) { //build an index of blocks
      this.blocks[node.getAttribute("block")] = eval("new " + node.getAttribute("block"));
      this.blocks[node.getAttribute("block")].me = node.getAttribute("block"); //for debugging
      if (node.getAttribute("block").indexOf("Block(") != -1){
        /* Make sure all blocks and data sources are loaded, then call my OnLoaded event */
        this.blocks[node.getAttribute("block")].WhenLoaded(this.checklist.Check(node.getAttribute("block")));
      }
    }
  }
  
  if (node.nodeName == "data"){
    var nodeID = node.getAttribute("id")
    if (!nodeID){
      throw new Error("Templating instructions: all data sources must have an id attribute");
    }

    var dataCallback = 

    /* Make sure all blocks and data sources are loaded, then call my OnLoaded event */
    data = new DataSource();
    if (url = node.getAttribute("src")){
      data.GetXML(this.GetLocation(url), "");
    }
    else if (procedure = node.getAttribute("procedure")){
      data.GetProcedure(procedure, "")
    }
    else {
      throw new Error("Templating instructions: all data sources must have a src, static, or datasource attribute");
    }
    data.WhenLoaded(Combine(this.Bind(this.DataLoaded, nodeID, data), this.checklist.Check("data" + nodeID)));
  }
  
  if (node.nodeName == "script"){
    var src = node.getAttribute("src");
    if (!src){
      throw new Error("Templating instructions: script commands must have an src attribute");
    }
    var url = this.GetLocation(src);
    if (!UsedScripts[url]){
      UsedScripts[url] = 1;
      var data = new DataSource();
      data.GetText(url, "");
      data.WhenLoaded(Combine(this.Bind(this.OnScriptLoaded, data), this.checklist.Check("script" + url)));
    }
  }
  
  if (node.nodeName == "style"){
    var src = node.getAttribute("src");
    if (!src){
      throw new Error("Templating instructions: style commands must have an src attribute");
    }

    var url = this.GetLocation(src);

    if (!UsedCSS[url]){
      var html_head = GetHead();
      var mylink = document.createElement('link');
      mylink.setAttribute("href", url);
      mylink.setAttribute("type", "text/css");
      mylink.setAttribute("rel", "stylesheet");
      html_head.appendChild(mylink);
      UsedCSS[url] = 1;
    }
  }
  
  for (var i = 0; i < node.childNodes.length; i++) {
    if (node.childNodes[i].nodeType == 1) {  //nodeType 1 is an ELEMENT_NODE
      this.PrepareTemplating(node.childNodes[i], templater);
    }
  }
}

Block.prototype.OnScriptLoaded = function (data) {
  LoadModule(data.text);
}

Block.prototype.OnDataLoaded = function (id, data) {
  this.data[id] = data.xml;
}




/************************************************************************************
* Block.CreateFunction()
* The purpose of this is to speed up the processing of thousands of rows.  This way,
* the structure of the XML is turned into a function, so we don't have to cycle through
* and interprate the XML for each record.
***************************************************************************************/

Block.prototype.CreateFunction = function(node, depth){
  var retval = "";
  if (depth == null){ //handle default
    depth = 0;
  }
  if (node.nodeName == "class"){
    return "new_" + (depth - 1) + '.Insert("' + node.getAttribute("targetid") + ":" + 'onload", "return new ' + node.getAttribute("class") + '(this, " + StaticReference(this.data) + ", childClasses, " + StaticReference(this.arguments) + ")");'; //TODO:make this give the specific data for the current level of recursion, not just the whole data set
  }
  if (node.nodeName == "replace"){
    var datasource = "";
    if (depth > 1){
      datasource = 'tags_' + (depth - 1) + '[i_' + (depth - 1) + ']'
    }
    else{
      datasource = 'this.data.input';
    }
    return 'new_' + (depth - 1) + '.Insert("' + node.getAttribute("targetid") + '", this.blocks["' + node.getAttribute("block") + '"].Make(' + datasource  + '));\n';
  }
  if (node.nodeName == "template"){
    retval = 'this._Make = function() {\n';

    retval += 'var new_0 = this.template.GetOutput();\n'
  }
  else if (node.nodeName == "repeat"){
    if (node.getAttribute("data")){
      retval += 'if (this.data["' + node.getAttribute("data") + '"]) {\n';
      retval += 'var tags_' + depth + '= this.data["' + node.getAttribute("data") + '"].getElementsByTagName("' + node.getAttribute("tag") + '");\n';
      retval += '} else { var tags_' + depth + ' = []; }';
    }
    else {
      retval += 'var tags_' + depth + '= tags_' + (depth - 1) + '[i_' + (depth -1) + '].getElementsByTagName("' + node.getAttribute("tag") + '");\n';
    }
    retval  += 'for (var i_' + depth + ' = 0; i_' + depth + '  < tags_' + depth + '.length; i_' + depth + '++){\n'
    retval  += 'var new_' + depth + ' = new_' + (depth - 1) + '.Add("' + node.getAttribute("targetid") + '");\n';
  }

  for (var i = 0; i < node.childNodes.length; i++){
    if (node.childNodes[i].nodeType == 1) { // nodeType 1 is ELEMENT_NODE
      retval += this.CreateFunction(node.childNodes[i], depth + 1);
    }
  }

  if (node.nodeName == "repeat"){
    retval += "}\n";
  }

  if (node.nodeName == "template"){
    retval += "return new_0;\n";
    retval += "}\n";
    eval(retval);
  }
  return retval;
}

Block.prototype.Make = function (data) {
  if(data && data.length){
    for (var source in data){
      this.data[source] = data[source];
    }
  }
  else{
    this.data["input"] = data;
  }
  return this._Make();
}


Block.prototype.MakeNode = function () {
  var html = this.Make.apply(this, arguments).toString(); //this.Make is called with all the arguments passed to MakeNode
  var mynode = nodeFromHtml(html);
  this.attachedClasses = ExecuteOnloads(mynode);
  return mynode;
}

// }}}

// nodeFromHtml and friends {{{
function nodeFromHtml (html) {
  var mynode = document.createElement("span");
  
  var tagname = GetFirstTag(html);

  // Because different browsers will mangle fragments of tables in different ways, we have to put the xml
  // within their proper parents to keep it from being changed as it is parsed
  if (tagname == "tr") {
    mynode.innerHTML = '<table><tbody>' + html + '</tbody></table>';
    var depth = 3;
  }
  else if (tagname == "td" || tagname == "th" ){
    mynode.innerHTML = '<table><tbody><tr>' + html + '</tr></tbody></table>';
    var depth = 4;
  }
  else if (tagname == "tbody" || tagname == "thead" || tagname == "tfoot" || tagname == "caption" || tagname == "colgroup") {
    mynode.innerHTML = "<table>" + html + "</table>";
    var depth = 2;
  }
  else if (tagname == "col"){
    mynode.innerHTML = "<table><colgroup>" + html + "</colgroup></table>";
    var depth = 3;
  } 
  else {
    mynode.innerHTML = html;
    var depth = 1;
  }
  
  while (depth-- > 0 && mynode){
    mynode = FirstElementOf(mynode);
    if (mynode == null) {
      throw new Error("MakeNode: Unexpected end of child nodes.  Parsing is not working.");
    }
  }

  return mynode;
}
  
/*************************************************************************
* Helper functions used by nodeFromHtml
*************************************************************************/

function GetFirstTag(string){
  var start = string.indexOf("<");
  if(start == -1){
    return "";
  }
  var end = start + 1;
  while(string.charAt(end) != " " && string.charAt(end) != "\t" && string.charAt(end) != "/" && string.charAt(end) != ">" ){
    end++;
    if (end >= string.length) { 
      return "";
    }
  }
  var tagname = string.substring(start + 1, end);
  return tagname.toLowerCase();
}

function FirstElementOf(tag){
  for (var i = 0; i < tag.childNodes.length; i++){
    if (tag.childNodes[i].nodeType == 1){
      return tag.childNodes[i];
    }
  }
  return undefined;
}

// }}}

// Templating {{{
/******************************************************
  TemplateX Class - Creates TemplateXOutput from
  Extract directives
*******************************************************/

function TemplateX() {
  this.OwnerDoc = null;
  this.doc = null;
  this.Fragments = new Object();
  this.Processed = false;
  this.list = new Array();
  this.idVault = []; // Filled with id attributess found in the XML
}

TemplateX.prototype.storeIds = function(node) {
  if (node.nodeType == 1) { 
    if(node.getAttribute("id")){ //element node
      this.idVault[node.getAttribute("id")] = node;
    }
    for (var i = 0; i < node.childNodes.length; i++){
      if (node.childNodes[i].nodeType == 1) { //1 is ELEMENT_NODE
        this.storeIds(node.childNodes[i]);
      }
    }
  }
}

TemplateX.prototype.getElementById = function(id){
  return this.idVault[id];
}

TemplateX.prototype.CreateFromNode = function(node) {
  this.doc = node;
  this.OwnerDoc = this.doc.ownerDocument;
}

TemplateX.prototype.CreateFromDocument = function(doc) {
  this.doc = doc;
  this.OwnerDoc = doc;
  for (var i = 0; i < doc.childNodes.length; i++){
    this.storeIds(doc.childNodes[i]);
  }
}

TemplateX.prototype.Extract = function(id) {
  var extractedNode = this.getElementById(id); //use our own getElementById that uses an ID hashtable we generated
  if (! extractedNode){
    throw new Error("Templating Error:  Trying to extract non-existant node with id '" + id + "'");
  }
  this.Fragments[id] = new TemplateX();
  this.Fragments[id].CreateFromNode(extractedNode);
  this.Fragments[id].idVault = this.idVault;  //they share the same array of ids

  /* We replace the extracted element with a span with the same id.  That span contains the text
   (((Placeholder:XXXXXX))).  The span and the text it contains is later replaced with HTML generated through
   Adding, Inserting and Replacing with TemplateXOutput. */
  var PlaceHolderSpan = this.OwnerDoc.createElement("span");
  PlaceHolderSpan.appendChild(this.OwnerDoc.createTextNode("(((Placeholder:" + id + ")))"));
  extractedNode.parentNode.replaceChild(PlaceHolderSpan, extractedNode);
  return this.Fragments[id];
}

TemplateX.prototype.ExtractAttribute = function(id, attribute) {
  var extractedNode = this.getElementById(id); //use our own getElementById that uses an ID hash we generated
  if (! extractedNode){
    throw new Error("Templating Error:  Trying to extract non-existant node with id '" + id + "'");
  }
  idname = id + ":" + attribute;
  this.Fragments[idname] = new TemplateX();
  this.Fragments[idname].Processed = true;  //the fragment has no associated xml node, so we don't want it to get processed.  It is just a placeholder
  extractedNode.setAttribute(attribute, "(((Placeholder:" + idname + ")))");
}

TemplateX.prototype.ExtractMany = function(ids){
  for (var i = 0; i < ids.length; i++){
    this.Extract(ids[i]);
  }
}

TemplateX.prototype.Clear = function(){
  //TODO: reduces memory usage? not sure if this works
  this.doc = null;
}

TemplateX.prototype.Process = function(){
  if (!this.Processed){
    var txt = new XMLSerializer().serializeToString(this.doc);
    var pos = 0;
    var parts = txt.split("(((Placeholder:");
    if (parts[0].substr(parts[0].length - 6, 6) == "<span>"){ //only elements will have a span around them, not attributes
      parts[0] = parts[0].substr(0, parts.length - 6);
    }
    this.list.push(parts[0]);
    for (var i = 1; i < parts.length; i++){
      var end = parts[i].indexOf(")))");
      var id = parts[i].substr(0, end); //the stuff within <span>(((Placeholder:____)))
      var rest = parts[i].substr(end + 3); //everything after the )))</span> until the next (((Placeholder:
      this.list.push(id);
      
      if (rest.substr(0, 7) == "</span>") {
        rest = rest.substr(7);
      }
      if (rest.substr(rest.length - 6, 6) == "<span>"){ //only elements will have a span around them, not attributes
        rest = rest.substr(0, rest.length - 6);
      }
      this.list.push(rest);
    }

    for (var key in self.Fragments){ //process all fragments
      this.Fragments[key].Process();
    }
    this.Clear();
    this.Processed = true;
  }
}

TemplateX.prototype.GetOutput = function() {
  if (!this.Processed){
    this.Process();
  }

  var output = new TemplateXOutput();
  output.SetStrings(this.list);

  for (var key in this.Fragments) {
    var newFrag = this.Fragments[key].GetOutput();
    output.Fragments[key] = [newFrag];
  }

  return output;
}
/******************************************************
  TemplateXOutput Class - Performs repetitions,
  replacements, and output
*******************************************************/

function TemplateXOutput(){
  this.Fragments = new Object();
  this.strings = new Array();
}

TemplateXOutput.prototype.SetStrings = function(list) {
  this.strings = list.slice(); //slice does a copy
}

TemplateXOutput.prototype.Add = function(id) {
  if (!this.Fragments[id]){
    throw new Error("Templating: Trying to add fragment that was never extracted with id '" + id + "'");
  }

  var duplicate = this.Fragments[id][0].CopyMe(); //copy the first fragment of that type, which is the "template"
  this.Fragments[id].push(duplicate);
  return duplicate;
}

TemplateXOutput.prototype.Insert = function(id, textValue) {
  //this function inserts text (including HTML text) at the location that the item with id was extracted
  if (!this.Fragments[id]){
    throw new Error("Templating: Trying to insert fragment that was never extracted with id '" + id + "'");
  }
  this.Fragments[id].push(textValue);
}


TemplateXOutput.prototype.CopyMe = function(){
  var NewX = new TemplateXOutput();
  NewX.SetStrings(this.strings);
  for (var key in this.Fragments){
    NewX.Fragments[key] = [];
    for (var frag in this.Fragments[key]) {
      NewX.Fragments[key].push(this.Fragments[key][frag]);
    }
  }
  return NewX;
}

/* Write the templated xml using the write method of
   the writer passed to the function.  Works well in IE,
   but not Firefox
*/

TemplateXOutput.prototype.Dump = function(writer) {
  for (var n = 0; n < this.strings.length; n++) {
    if ((n % 2) == 0) {
      writer.write(this.strings[n]);
    }
    else {//this is a replacement field
      var frags = this.Fragments[this.strings[n]];
      for (var i = 1; i < frags.length; i++) {//skip the first TemplateXOutput objects, which is the "template"
        if (frags[i].Dump){
          frags[i].Dump(writer);
        }
        else{ //this is just a string
          writer.write(frags[i] + "*");
        }
      }
    }
  }
}

/* Write the templated XML to a string.  Works well in Firefox, but
   not IE */

TemplateXOutput.prototype.toString = function() {
  var result = ""
  for (var n = 0; n < this.strings.length; n++) {
    if ((n % 2) == 0) {
      result += this.strings[n];
    }
    else {//this is a replacement field
      var frags = this.Fragments[this.strings[n]];
      for (var i = 1; i < frags.length; i++) {//skip the first TemplateXOutput objects, which is the "template"
        result += frags[i].toString();
      }
    }
  }
  return result;
}

// }}}

// Hard coded Blocks {{{

function Text(fieldname){
  this.fieldname = fieldname;
}

Text.prototype.Make = function (xmlNode){
  if (xmlNode) {
    var attr = xmlNode.getAttribute(this.fieldname);
    if (attr) {
      return attr;
    }
  }
  return "";
}

function TimePart(fieldname) {
  this.fieldname = fieldname;
}

TimePart.prototype.Make = function (xmlNode) {
  var attr = xmlNode.getAttribute(this.fieldname);
  if (attr) {
    return attr.substr(11, 5);
  }
}

function Link(url, name){
  this.url = url;
  this.name = name;
}

Link.prototype.Make = function (xmlNode){
  return '<a href="' +  xmlNode.getAttribute(this.url) + '">' + xmlNode.getAttribute(this.name) + '</a>';
}

function SqlDate (timestring) {
  unixtime = Date.parse(timestring);
  if (!unixtime) {
    dt = new Date();
    timestring = dt.toLocaleDateString() + " " + timestring;
    unixtime = Date.parse(timestring);
    if (!unixtime) {
      return "0000-00-00 00:00:00";
    }
  }
  dt.setTime(unixtime);
  return dt.getFullYear() + "-" + ZeroPad(dt.getMonth() + 1, 2) + "-" + ZeroPad(dt.getDate(), 2) + " " + ZeroPad(dt.getHours(), 2) + ":" + ZeroPad(dt.getMinutes(), 2) + ":" + ZeroPad(dt.getSeconds(), 2); 
}

Date.prototype.parseDate = function (datestring) {
  var unixtime = Date.parse(datestring);
  if (!unixtime) {
    var dt = new Date();
    datestring = dt.toLocaleDateString() + " " + datestring;
    unixtime = Date.parse(datestring);
    if (!unixtime) {
      return false;
    }
  }
  this.setTime(unixtime);
  return true;
}

Date.prototype.parseSql = function (sqldate){
  this.setYear(sqldate.substr(0, 4));
  this.setMonth(Number(sqldate.substr(5, 2)) - 1);
  this.setDate(sqldate.substr(8, 2));
  this.setHours(sqldate.substr(11, 2));
  this.setMinutes(sqldate.substr(14, 2));
  this.setSeconds(sqldate.substr(17, 2));
}

Date.prototype.getSqlDate = function () {
  return this.getFullYear() + "-" + ZeroPad(this.getMonth() + 1, 2) + "-" + ZeroPad(this.getDate(), 2);
}

Date.prototype.getSqlTime = function () {
  return ZeroPad(this.getHours(), 2) + ":" + ZeroPad(this.getMinutes(), 2) + ":" + ZeroPad(this.getSeconds(), 2); 
}

Date.prototype.getSqlDateTime = function () {
  return this.getSqlDate() + " " + this.getSqlTime();
}
Date.prototype.getMonthDayYear = function () {
  return ZeroPad(this.getMonth() + 1, 2) + "/" + ZeroPad(this.getDate(), 2) + "/" + this.getFullYear();
}
Date.prototype.getShortTime = function () {
  return ZeroPad(this.getHours(), 2) + ":" + ZeroPad(this.getMinutes(), 2);
}

// }}}

// Parse querystrings {{{
/*******************************************************
  Parse querystrings

 Client-side access to querystring name=value pairs
	Version 1.2.3
	22 Jun 2005
	Adam Vandenberg
********************************************************/
function Querystring(qs) { // optionally pass a querystring to parse
	this.params = new Object()
	this.get=Querystring_get

	if (qs == null)
		qs=location.search.substring(1,location.search.length)

	if (qs.length == 0) return

// Turn <plus> back to <space>
// See: http://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13.4.1
	qs = qs.replace(/\+/g, ' ')
	var args = qs.split('&') // parse out name/value pairs separated via &

// split out each name=value pair
	for (var i=0;i<args.length;i++) {
		var value;
		var pair = args[i].split('=')
		var name = unescape(pair[0])

		if (pair.length == 2)
			value = unescape(pair[1])
		else
			value = name

		this.params[name] = value
	}
}

function Querystring_get(key, default_) {
	// This silly looking line changes UNDEFINED to NULL
	if (default_ == null) default_ = null;

	var value=this.params[key]
	if (value==null) value=default_;

	return value
}

// }}}

// Hotkey functions {{{
/********************************************************************************
  Hotkey functionality -- detect and respond to key presses easily
  To use, define keypressed(key, alt, ctrl, shift) to repond appropriately
********************************************************************************/

function HotKeyManager (target) {
  target.onkeypress = Combine(target.onkeypress, this.Bind(this.OnKeyPress));
  this.keys = {};
}

HotKeyManager.Inherits(block_class);

HotKeyManager.prototype.OnKeyPress = function(e) {
  if (!e) { e = window.event };
  
  var keystring = "";
  if (e.ctrlKey)  { keystring += "c" };
  if (e.altKey)   { keystring += "a" };
  if (e.shiftKey) { keystring += "s" };
  keystring += String(e.which);
  if (this.keys[keystring]) {
    return this.keys[keystring](e);
  }
  
  return true;
}

HotKeyManager.prototype.Add = function (key, handler, exclusive){
  if (exclusive) { 
    this.keys[key] = handler;
  }
  else {
    this.keys[key] = Combine(this.keys[key], handler);
  }
}

HotKeyManager.prototype.Remove = function (key) {
  this.keys[key] = undefined;
}

HotKeyManager.prototype.Clear = function (key) {
  this.keys = {};
}

HotKeyManager.prototype.GetKey = function (keychar) {
  return keychar.charCodeAt(0);
}


HotKeys = new HotKeyManager(window);

//}}}

// Combine() {{{
/*******************************************************************************
* Combine 2 functions into 1 function.  Can be called multiple times to string 
* together many functions CombineFunction(CombineFunction(a, b), c)
* Used to add additional actions to an event without overwriting old ones 
*******************************************************************************/
function Combine(function1, function2){
  if (!function1){
    return function2;
  }
  if (!function2){
    return function1;
  }
  return function() {
    function1.apply(this, arguments);
    function2.apply(this, arguments);
  }
}
// }}}

// getStyleClass() {{{
/*******************************************************************************
  Get a css style by class name
*******************************************************************************/

function getStyleClass (className) {
  for (var s = 0; s < document.styleSheets.length; s++)
  {
    if(document.styleSheets[s].rules)
    {
      for (var r = 0; r < document.styleSheets[s].rules.length; r++)
      {
        if (document.styleSheets[s].rules[r].selectorText == '.' + className)
        {
          return document.styleSheets[s].rules[r];
        }
      }
    }
    else if(document.styleSheets[s].cssRules)
    {
      for (var r = 0; r < document.styleSheets[s].cssRules.length; r++)
      {
        if (document.styleSheets[s].cssRules[r].selectorText == '.' + className)
          return document.styleSheets[s].cssRules[r];
      }
    }
  }
  
  return null;
}

// }}}

// WindowHeight() and WindowWidth() {{{
/**********************************************************************************
  Get the inner dimensions of the current window
**********************************************************************************/

function WindowHeight() {
  var myWidth = 0, myHeight = 0;
  if( typeof( window.innerWidth ) == 'number' ) {
    //Non-IE
    myHeight = window.innerHeight;
  } else if( document.documentElement && ( document.documentElement.clientWidth || document.documentElement.clientHeight ) ) {
    //IE 6+ in 'standards compliant mode'
    myHeight = document.documentElement.clientHeight;
  } else if( document.body && ( document.body.clientWidth || document.body.clientHeight ) ) {
    //IE 4 compatible
    myHeight = document.body.clientHeight;
  }
  return myHeight;
}

function WindowWidth() {
  var myWidth = 0, myHeight = 0;
  if( typeof( window.innerWidth ) == 'number' ) {
    //Non-IE
    myWidth = window.innerWidth;
  } else if( document.documentElement && ( document.documentElement.clientWidth || document.documentElement.clientHeight ) ) {
    //IE 6+ in 'standards compliant mode'
    myWidth = document.documentElement.clientWidth;
  } else if( document.body && ( document.body.clientWidth || document.body.clientHeight ) ) {
    //IE 4 compatible
    myWidth = document.body.clientWidth;
  }
  return myWidth;
}

// }}}

// Not Used - Hash navigation {{{
function SetHash(newval) {
  location.hash = newval;
}

function GetHash(){
  return location.hash.substr(1);
}

function ReloadPage() {
  var hashvalue = GetHash();
  if (!hashvalue){
    SetHash("alert('test')");
  }
  else{
    eval(hashvalue);
  }
}

// }}}
