//
// ******************************************************************************************************
// *                                                                                                    *
// *                                  Javascript Classes and Functions                                  *
// *                                                                                                    *
// *  Class                                           Base Class                                        *
// *                                                                                                    *
// *    ShowPage                                        (n/a)                                           *
// *    ShowPage_Load                                   (n/a)                                           *
// *                                                                                                    *
// ******************************************************************************************************
//

//
// ******************************************************************************************************
// *                                                                                                    *
// * Class ShowPage                                                                                     *
// *                                                                                                    *
// ******************************************************************************************************
//
// constructor
//
function ShowPage() { }
//
// protected (initializer)
//
ShowPage.prototype.init = function() { };
//
// public
//
ShowPage.prototype.sprint   = function() { return "ShowPage {" + this.toString() + "\n}" };
ShowPage.prototype.toString = function() { return "" };

//
// ******************************************************************************************************
// *                                                                                                    *
// * A cache for all kinds of page-related objects to be added by other classes                         *
// *                                                                                                    *
// ******************************************************************************************************
//
ShowPage.cache = {};

//
// ******************************************************************************************************
// *                                                                                                    *
// * Microsoft XML DOM document class names, in order of preference (borrowed from xmlrpc)              *
// *                                                                                                    *
// ******************************************************************************************************
//
ShowPage.msXmlDocument = [
  "MSXML2.DOMDOCUMENT.6.0",
  "MSXML2.DOMDOCUMENT.3.0",
  "MSXML2.DOMDOCUMENT.4.0",
  "MSXML2.DOMDOCUMENT",
  "Microsoft.XMLDOM"
];

//
// ******************************************************************************************************
// *                                                                                                    *
// * Detect single versus double clicks (static data and functions).                                    *
// *                                                                                                    *
// ******************************************************************************************************
//
ShowPage.ignoreSingleClick    = false;
ShowPage.doAllowSingleClick   = function() { return ShowPage.ignoreSingleClick = false; };
ShowPage.doIgnoreSingleClick  = function() { return ShowPage.ignoreSingleClick = true ; };
ShowPage.mayIgnoreSingleClick = function() { return ShowPage.ignoreSingleClick ;        };

//
// ******************************************************************************************************
// *                                                                                                    *
// * Functions that have side effects on the HTML DOM (static).                                         *
// *                                                                                                    *
// ******************************************************************************************************
//

ShowPage.submitOnce = function(s, id, eventObj) {
  //
  // Prevent redundant submission of forms:
  //
  //   (1) Disable the button/submit, input/submit or input/image element s,
  //       and all other button/submit, input/submit and image/submit elements, while we're at it.
  //   (2) Temporarily set the value of the input/hidden element id,
  //       so that its value will be submitted as POST["Submit"] = s.name.
  //   (3) Submit the form.
  //
  if (!eventObj) eventObj = window.event;
  if (eventObj.stopPropagation) eventObj.stopPropagation(); else eventObj.cancelBubble = true;
  s.disabled = true;
  var tag = window.document.getElementsByTagName("button");
  for (var i=0; i<tag.length; i++)
    if (tag.item(i).type.toLowerCase() == "submit") tag.item(i).disabled = true;
  var tag = window.document.getElementsByTagName("input");
  if (typeof tag.item != "function") { //***** Bug in Opera 9.50
    for (var i=0; i<tag.length; i++)
      if (tag[i].type.toLowerCase().match(/^(submit|image)$/)) tag[i].disabled = true;
  } else {
    for (var i=0; i<tag.length; i++)
      if (tag.item(i).type.toLowerCase().match(/^(submit|image)$/)) tag.item(i).disabled = true;
  }
  var x = id == "" ? null : window.document.getElementById(id); if (x) x.value = s.name;
  s.form.submit();
  return true;
};

ShowPage.checkFloat = function(id, eventObj) {
  //
  // Get rid of everything except for digits and one decimal point.
  // Force at least one digit before the decimal point and exactly two digits after the decimal point.
  //
  if (!eventObj) eventObj = window.event;
  if (eventObj.stopPropagation) eventObj.stopPropagation(); else eventObj.cancelBubble = true;
  var textValue = window.document.getElementById(id);
  var oldText = textValue.value, newText = "", digits = ".0123456789";
  for (var i=0, before=0, after=0, dec=0; i<oldText.length; i++) {
    var loc = digits.indexOf(oldText.substr(i,1));
    if (loc == 0) {
      if (dec == 0)       { newText += "."; dec++; }                    // Append a decimal point.
    } else if (loc > 0) {
      if (dec == 0)       { newText += oldText.substr(i,1); before++; } // Append pre-decimal digits.
      else if (after < 2) { newText += oldText.substr(i,1); after++;  } // Append post-decimal digits.
    }
  }
  while (before > 1 && newText.substr(0,1) == "0")                      // Delete redundant
    { newText = newText.substr(1); before--; }                          // leading zeros.
  if (before == 0)        { newText = "0" + newText; before++; }        // Prepend a zero.
  if (dec == 0)           { newText += "."; dec++; }                    // Append a decimal point.
  while (after < 2)       { newText += "0"; after++; }                  // Append trailing zeros.
  if (newText != oldText) textValue.value = newText;
};

ShowPage.checkZIP = function(id, eventObj) {
  //
  // Get rid of everything except for five digits.  Force at exactly two digits.
  //
  if (!eventObj) eventObj = window.event;
  if (eventObj.stopPropagation) eventObj.stopPropagation(); else eventObj.cancelBubble = true;
  var textValue = window.document.getElementById(id);
  var oldText = textValue.value, newText = "", digits = "0123456789";
  for (var i=count=0; i<oldText.length; i++) if (digits.indexOf(oldText.substr(i,1)) >= 0 && count < 5)
    { newText += oldText.substr(i,1); count++; }
  while (count < 5) { newText = "0" + newText; count++; } // Prepend zeros.
  if (newText != oldText) textValue.value = newText;
};

//
// ******************************************************************************************************
// *                                                                                                    *
// * Miscellaneous functions (static).                                                                  *
// *                                                                                                    *
// ******************************************************************************************************
//
ShowPage.htmlentities = function(x) {
  //
  // Equivalent to the PHP function htmlentities(x, ENT_NOQUOTES, "UTF-8")
  // Guaranteed to be the inverse of ShowPage.html_entity_decode(string), below.
  //
  if (x === null) return "null";
  else switch (typeof x) {
  case "object" : return x.toString().replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
  case "string" : return x.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
  default       : return String(x);
  }
};

ShowPage.html_entity_decode = function(x) {
  //
  // Equivalent to the PHP function html_entity_decode(x, ENT_NOQUOTES, "UTF-8")
  // Guaranteed to be the inverse of ShowPage.htmlentities(string), above.
  //
  return x.toString().replace(/&gt;/g,">").replace(/&lt;/g,"<").replace(/&amp;/g,"&");
};

ShowPage.numeric_html_entity_decode = function(x, what) {
  //
  // Translate "&#n;", where n is one or more decimal digits, into the corresponding character.
  //
  var x = x.toString().split('&#'), regs;
  if (what === undefined) var what = 'control';
  switch (what) {
  case 'all' :
    //
    // Javascript uses UTF-16, so don't worry about large-ish values of n.
    //
    for (var i=0; i<x.length; i++) if (i == 0);
    else if (regs = x[i].match(/^(\d+);(.*)/))
         x[i] = String.fromCharCode(parseInt(regs[1],10)) + regs[2];
    else x[i] = '&#' + x[i];
    break;
  case 'control' :
  default        :
    //
    // Translate "&#0;" through "&#31;" (except for "&#10;"), "&#38;", "&60;" and "&62;"
    // into the corresponding control characters, "&", "<" and ">".
    //
    for (var i=0; i<x.length; i++) if (i == 0);
    else if (regs = x[i].match(/^(\d|1[1-9]|2\d|3[018]|6[02]);(.*)/))
         x[i] = String.fromCharCode(parseInt(regs[1],10)) + regs[2];
    else x[i] = '&#' + x[i];
    break;
  }
  return x.join("");
};

ShowPage.redOrGreen = function(format, x) {
  if      (x < 0) return '<span class="no">'  + sprintf(format, x) + '</span>';
  else if (x > 0) return '<span class="yes">' + sprintf(format, x) + '</span>';
  else            return sprintf(format, x);
};

ShowPage.stripTags = function(x) {
  //
  // Strip out html tags from a string.  There is probably a better way to do this.
  //
  return x.replace(/<(\S+)[^>]*>([^<>]*)<\/\1>/g, "$2");
};

ShowPage.sprint = function(x) { return typeof x.sprint == "function" ? x.sprint() : String(x); };

ShowPage.sprint_r = function(x) {
  //
  // Format an Array, object or any simple type.
  //
  var outer = function(x) {
    var inner = function(x) {
      if (x === null)
        return "null";
      else switch (typeof x) {
      case "object" :
        return " (object)";
      case "string" :
        return ShowPage.htmlentities(Utf8.decode(x)) + " (string)";
      case "function" :
        return " (function)";
      default :
        return x + " (" + typeof x + ")";
      }
    };
    if (x === null) return "null";
    else switch (typeof x) {
    case "object" :
      if (x instanceof Array) {
        var s = "Array {", and=" ";
        for (var j=0; j<x.length; j++) {
          s += and + "[" + j + "] => " + inner(x[j]);
          and = ",\t";
        }
        return s + " }";
      } else {
        var s = "object {", and = " ";
        for (var j in x) {
          s += and + "[" + j + "] => " + inner(x[j]);
          and = ",\t";
        }
        return s + " }";
      }
    case "string" :
      return ShowPage.htmlentities(Utf8.decode(x)) + " (string)";
    case "function" :
    case "undefined" :
      return typeof x;
    default :
      return x + " (" + typeof x + ")";
    }
  };
  if (x === null) return "null\n";
  else switch (typeof x) {
  case "object" :
    if (x instanceof Array) {
      var s = "Array {\n";
      for (var i=0; i<x.length; i++)
        s += "  [" + i + "] => " + outer(x[i]) + "\n";
      return s + "}\n";
    } else {
      var s = "object {\n";
      for (var i in x)
        s += "  [" + i + "] => " + outer(x[i]) + "\n";
      return s + "}\n";
    }
  case "string" :
    return ShowPage.htmlentities(Utf8.decode(x)) + " (" + typeof x + ")\n";
  case "function" :
  case "undefined" :
    return typeof x + "\n";
  default :
    return x + "(" + typeof x + ")\n";
  }
};

//
// ******************************************************************************************************
// *                                                                                                    *
// * Class ShowPage_Load                                                                                *
// *                                                                                                    *
// *   Serialize the loading of images, iframes and inserted code.                                      *
// *                                                                                                    *
// ******************************************************************************************************
//
// constructor
//
function ShowPage_Load(tag, id, src) {
  if (arguments.length == 0 || tag === null) {
  } else if (typeof tag != "object") {
    this.init.apply(this, arguments);
  } else if (tag instanceof ShowPage_Load) {
    this.init(tag.tag, tag.id, tag.src);
  } else {
    this.init(tag["tag"], tag["id"], tag["src"]);
  }
}
//
// protected (initializer)
//
ShowPage_Load.prototype.init = function(tag, id, src) {
  this.tag = tag !== undefined ? tag : "";
  this.id  = id  !== undefined ? id  : "";
  this.src = src !== undefined ? src : "";
};
//
// public
//
ShowPage_Load.prototype.sprint   = function() { return "ShowPage_Load {" + this.toString() + "\n}"; };
ShowPage_Load.prototype.toString = function() {
  var s = "";
  if (this.tag !== undefined) s += "\n  [tag] => " + this.tag;
  if (this.id  !== undefined) s += "\n  [id]  => " + this.id;
  if (this.src !== undefined) s += "\n  [src] => " + this.src;
  return s;
};
//
// static
//
ShowPage_Load.toLoad   = [];
ShowPage_Load.toUpdate = [];
ShowPage_Load.pushLoad = function(tag, id, src) {
  ShowPage_Load.toLoad.push(new ShowPage_Load(tag, id, src));
  return null;
};
ShowPage_Load.pushUpdate = function(tag, id, src) {
  ShowPage_Load.toUpdate.push(new ShowPage_Load(tag, id, src));
  return null;
};
ShowPage_Load.load = function(eventObj) {
  if (!eventObj) eventObj = window.event;
  if (eventObj.stopPropagation) eventObj.stopPropagation(); else eventObj.cancelBubble = true;
  return ShowPage_Load.next();
};
ShowPage_Load.update = function() {
  ShowPage_Load.toLoad   = ShowPage_Load.toLoad.concat(ShowPage_Load.toUpdate);
  ShowPage_Load.toUpdate = [];
  return ShowPage_Load.next();
};
ShowPage_Load.next = function() {
  while (ShowPage_Load.toLoad.length) {
    var doIt = ShowPage_Load.toLoad.shift(), e;
    switch (doIt.tag) {
    case "function" :
      //
      // Don't expect this function to produce another "onload" event.
      // Instead, expect it to finish loading and only then return.
      //
      if (typeof doIt.src == "function") doIt.src(doIt.id);
      break;
    case "embed" :
    case "iframe" :
    case "img" :
      if (e = window.document.getElementById(doIt.id)) {
        //
        // This must produce another "onload" event, or we're stuck.
        //
        e.src    = doIt.src;
        e.onload = function(event) { return ShowPage_Load.load(event); };
        return true;
      }
      break;
    default :
      if (e = window.document.getElementById(doIt.id)) {
        //
        // This must produce another "onload" event, or we're stuck.
        //
        e.innerHTML = doIt.src;
        return true;
      }
      break;
    }
  }
  return true;
};

