/* ********************************************************************
 * Evertz Modular Bulk Design - Web Application
 *
 * Build up to 300 modular frames at once using this tool
 *
 * 90 frames is enough for anyone, right?  Wrong!
 * 102 frames is enough for anyone, right?  Wrong!
 * 300 frames is enough for anyone, right?  Well, I've been wrong before...
 *
 */

/* ***************************************************************************************************************************
 * Controlling application object
 *
 */
function Application() {
  var self = this;

  this.clipboard = [];
  this.recent = [];

  this.fpp = 6;
  this.pages = 1;
  this.page = 0;
  this.flimit = 300;
  this.working = 0;
  this.view = "edit";
  this.md5 = "";
  this.online = false;
  this.mirror = false;
  this.cursor = 0;
  this.wname = "configuration";
  this.type = "Bulk";

  this.keydown = false;
  this.keydelay = false;
  this.keyrepeat = false;

  this.dragging = false;
  this.multimove = 0;
  this.uploading = false;

  this.fopts = [];
  this.selkirk = 0;
  this.seltime = 0;
  this.seltip = 0;

  this.instWindow = false;

  this.infoips = { address: "0.0.0.0", subnetmask: "255.255.255.0", gateway: "0.0.0.0" };
  this.infofields = ["author", "company", "country", "project", "contact", "agent"];
  this.infomultis = ["ponumber", "esonumber"];
  this.trpattern = /^f([0-5])s(\d\d?)$/;


  /* ******************************************************************
   * Fill frame with currently selected module, or clipboard contents
   *
   */
  this.fill = function(typ) {
    switch (typ) {
      case "selected":
        if (frame[this.working].slot[this.cursor].name != "Empty Slot") {
          for (var i = 0; i < frame[this.working].slots; i++) {
            if (i != this.cursor && frame[this.working].slot[i].name == "Empty Slot") {
              if (frame[this.working].place(i, modHash[frame[this.working].slot[this.cursor].name], true)) {
                frame[this.working].slot[i].subTransfer(frame[this.working].slot[this.cursor]);
                i += frame[this.working].slot[i].slots - 1;
              }
            }
          } frame[this.working].refresh();
        } break;

      case "clipboard":
        if (this.clipboard.length) {
          var cursave = this.cursor;
          for (this.cursor = 0; this.cursor < frame[this.working].slots;)
            this.cursor += this.clip('paste', true);
          this.cursor = cursave;
          frame[this.working].refresh();
        } break;
    }
  };


  /* ******************************************************************
   * Controls Cut/Copy/Paste/Delete commands
   *
   */
  this.clip = function(action, silent) {
    this.hideDialogs();

    if (action == "copy" || action == "cut") {
      this.clipboard = [];
      for (var y = 0, x = 0; y < frame[this.working].slots;) {
        row = document.getElementById("f" + (this.working % this.fpp) + "s" + y).parentNode;
        if (row.className.indexOf('multiselect') != -1 || row.className.indexOf('selected') != -1)
          this.clipboard[x++] = cloneObject(frame[this.working].slot[y]);
        y += frame[this.working].slot[y].slots;
      }
      document.getElementById('scPaste').className = "";

    } else if (action == "paste") {
      for (var x = 0, y = 0; x < this.clipboard.length; x++) {
        if (this.cursor + y + this.clipboard[x].slots <= frame[this.working].slots) {
          for (var z = 0; z < this.clipboard[x].slots; z++) frame[this.working].place(this.cursor + y + z, 0, true);
          if (frame[this.working].place(this.cursor + y, modHash[this.clipboard[x].name], true))
            frame[this.working].slot[this.cursor + y].subTransfer(this.clipboard[x]);
        } y += this.clipboard[x].slots;
      }
      if (!silent) frame[this.working].refresh();
      return y;
    }

    if (action == "cut" || action == "delete") {
      for (var i = 0; i < frame[this.working].slots; i++) {
        var row = document.getElementById("f" + (this.working % this.fpp) + "s" + i).parentNode;
        if (row.className.indexOf('multiselect') > -1 || row.className.indexOf('selected') > -1)
          frame[this.working].place(i, 0, true);
      }
      if (!silent) frame[this.working].refresh();
    }
  };


  /* ******************************************************************
   * Duplicate this frame
   *
   */
  this.duplicate = function() {
    if (frame[this.working].isFull() & 6) {
      var targ = prompt("Enter a list of target frame numbers, or ranges, separated by spaces or commas (eg. 2,4-6,10).  Use the keyword \"all\" to target all frames.", "");
      if (targ) {
        if (targ.trim().toLowerCase() != "all") {
          targ = targ.replace(/ *- */g, "-").replace(/[^\d ,-]/ig, "").replace(/,/g, " ").trim().split(" ");
          for (var x = 0; x < targ.length; x++) {
            if (typeof targ[x] == "string" && targ[x].indexOf("-") > -1) {
              var rng = targ[x].split("-");
              targ.splice(x, 1)
              if (rng.length > 1) {
                var start = parseInt(rng[0]), end = parseInt(rng[rng.length - 1]);
                if (!isNaN(start) && !isNaN(end)) {
                  if (end < start) { var dummy = start; start = end; end = dummy; }
                  for (var y = start; y <= end; y++) targ.push(y);
                }
              }
            }
          }
        } else for (var x = 0, targ = []; x < frame.length; x++) targ[x] = x + 1;
        for (var x = 0, done = 0; x < targ.length; x++) {
          targ[x] = parseInt(targ[x]);
          if (!isNaN(targ[x]) && targ[x] > 0 && targ[x] <= this.fpp * this.pages && targ[x] - 1 != frame[this.working].id) {
            if (!(frame[targ[x] - 1].isFull() & 6) || confirm('Duplication will overwrite modified frame #' + targ[x] + ".  Okay?")) {
              var frameid = frame[targ[x] - 1].frameid;
              frame[targ[x] - 1] = cloneObject(frame[this.working]);
              frame[targ[x] - 1].id = targ[x] - 1;
              frame[targ[x] - 1].frameid = frameid;
              if (Math.floor(this.working / 6) == Math.floor((targ[x] - 1) / 6))
                frame[targ[x] - 1].refresh();
              done++;
            }
          }
        }
        if (done) alert(done + " duplicate(s) created");
      }
    } else alert("Selected frame is empty");
  }


  /* ******************************************************************
   * Add a page of six empty frames to the tool
   *
   */
  this.addFrames = function(silent) {
    if (this.pages < (this.flimit / 6)) {
      var span = document.createElement('span');
          span.appendChild(document.createTextNode((this.pages * this.fpp + 1) + "-" + (this.pages * this.fpp + this.fpp)));
          span.onclick = (function(x) { return function(e) { return self.switchPage(x); }})(this.pages);

      var fPages = document.getElementById('frames_pages');
          fPages.appendChild(document.createTextNode(", "));
          fPages.appendChild(span);

      if (!silent) fPages.slide('endright');
      for (var x = this.pages * this.fpp, ser = []; x < (this.pages + 1) * this.fpp; x++)
        ser.push(new Frame(x, "7800FR"));
      frame = frame.concat(ser);
      if (++this.pages > 16) {
        fPages.parentNode.className = "overflow";
        clearInterval(fPages.interval);
        if (!silent) {
          fPages.slideall = true;
          fPages.interval = setInterval(function() { fPages.slide('right'); }, 30);
        }
      }
      if (!silent) this.switchPage(this.pages - 1);
      return true;

    } else {
      if (!silent) alert('Cannot add any more frames to this configuration');
      return false;
    }
  };


  /* ******************************************************************
   * Move the view to a specific page of frames
   *
   */
  this.switchPage = function(page) {
    if (this.dragging) return false;
    if (this.page != page) {
      this.exitLabelMode();
      var pages = document.getElementById('frames_pages').getElementsByTagName('span');
      try { pages[this.page].className = ""; } catch(e) {}
      pages[this.page = page].className = "selected";
      this.working = this.page * this.fpp + this.working % this.fpp;
      for (var x = this.page * this.fpp; x < (this.page + 1) * this.fpp; x++) frame[x].refresh();
    }
    return true;
  };


  /* ******************************************************************
   * Collapse empty frames
   *
   */
  this.collapseEmpty = function() {
    for (var x = 0, ec = 0, mv = false; x < frame.length; x++) {
      if (frame[x].isFull()) {
        if (ec) {
          mv = true;
          frame[x - ec] = cloneObject(frame[x]);
          frame[x - ec].id = x - ec;
          frame[x - ec].frameid = (x - ec + 1) + "";
          frame[x] = new Frame(x, "7800FR");
        }
      } else ec++;
    }

    for (z = Math.floor(ec / this.fpp), fPages = document.getElementById('frames_pages'); z > 0 && this.pages > 1; z--, this.pages--) {
      mv = true;
      frame.splice(frame.length - 6, 6);
      fPages.removeChild(fPages.lastChild);
      fPages.removeChild(fPages.lastChild);
    }

    if (mv) {
      if (!this.page) this.page = -1;
      this.switchPage(0);
      if (this.pages <= 17) fPages.parentNode.className = "";
      fPages.style.left = "0px";
    } else alert('No unused frames to remove');
  };


  /* ******************************************************************
   * Append a PO/ESO row
   *
   */
  this.poesoRow = function(row) {
    var infoproj = document.getElementById('information_project');

    var vars = infoproj.getElementsByTagName('var');
    var newRow = vars[0].cloneNode(true);

    var button = newRow.getElementsByTagName('button')[0];
        button.onclick = function() { document.getElementById('information_project').removeChild(this.parentNode); };
        button.className = "butt20";
        button.title = "Delete this PO/ESO row";
        button.firstChild.nodeValue = "\u2013";
        button.onmouseover = function() { this.className += " active"; };
        button.onmouseout = function() { this.className = this.className.removeWord("active"); };

    var inputs = newRow.getElementsByTagName('input');
        inputs[0].value = inputs[1].value = "";
        cancelInputBubble(inputs[0]);
        cancelInputBubble(inputs[1]);

        infoproj.appendChild(newRow);
  };


  /* ******************************************************************
   * Convenience method to close all open dialogue windows
   *
   */
  this.hideDialogs = function() {
    for (var item in Pop) Pop[item].hide();
    document.getElementById('slotContext').style.visibility = "";
    document.getElementById('selectorContext').style.visibility = "";
    var manual = document.getElementById('manual');
    if (manual && manual.open) manual.animate(false, 10);
  };


  /* ******************************************************************
   * Raise or lower the module selection dialogue box
   *
   */
  this.selectorDisplay = function(display) {
    if (this.dragging) return false;
    var preview = document.getElementById('preview');
    var modfilt = document.getElementsByName("module_filter")[0];
    var slottype = (frame[this.working].image.length) ? frame[this.working].image[this.cursor].ptype : frame[this.working].type;

    if (display) {
      this.hideDialogs();
      modfilt.value = "";
      this.selectorFilter();
      Pop.selector.show();

      var nam = frame[this.working].slot[this.cursor].name;
      if (nam != "Empty Slot") {
        for (var x = 0; x < this.fopts.length; x++)
          if (this.fopts[x].firstChild.nodeValue == nam)
            { this.selkirk = x; break; }
        this.fopts[this.selkirk].className += " chosen";
        preview.src = modules[modHash[this.fopts[this.selkirk].firstChild.nodeValue]].fileName(slottype);
        var sellist = Pop.selector.element.getElementsByTagName('ul')[0];
        sellist.scrollTop = Math.max(0, this.selkirk * 16 - sellist.offsetHeight / 2 + 8);
      } else preview.src = modules[0].fileName(slottype);
      modfilt.focus();
    } else {
      Pop.selector.hide();
      modfilt.blur();
    }
  };


  /* ******************************************************************
   * Filter and display the list of available modules
   *
   */
  this.selectorFilter = function() {
    var modfilt = document.getElementsByName("module_filter")[0];
        modfilt.value = modfilt.value.toUpperCase();
    var pickitem = Pop.selector.element.getElementsByTagName('ul')[0].getElementsByTagName('li');
    var slottype = (frame[this.working].image.length) ? frame[this.working].image[this.cursor].ptype : frame[this.working].type;

    this.fopts = [];
    for (var x = 0; x < pickitem.length; x++) {
      pickitem[x].className = "";
      if ((!modfilt.value || pickitem[x].uname.indexOf(modfilt.value) != -1) && modules[modHash[pickitem[x].firstChild.nodeValue]].allowed(slottype, frame[this.working].frame, this.cursor)) {
        pickitem[x].style.display = "";
        this.fopts.push(pickitem[x]);
      } else pickitem[x].style.display = "none";
    } this.selkirk = -1;
    document.getElementById('preview').src = modules[0].fileName(slottype);
  };


  /* ******************************************************************
   * Raise or lower the module options selection dialogue box
   *
   */
  this.optionsDisplay = function(display) {
    if (this.dragging) return false;
    var cSlot = frame[this.working].slot[this.cursor];

    if (display) {
      this.hideDialogs();
      var optionsUL = Pop.options.element.getElementsByTagName("ul")[0], ti = 21;
      while (optionsUL.firstChild) optionsUL.removeChild(optionsUL.firstChild);

      if (cSlot.name.indexOf("xxx") > 0) {
        var li = document.createElement('li');
            li.className = "wdm";
          var h3 = document.createElement('h3');
              h3.appendChild(document.createTextNode("DWDM Wavelength"));
            li.appendChild(h3);
          var select = document.createElement('select');
              select.size = "1";
              select.name = "dwdm";
              select.tabIndex = ti++;
            for (var x = 200; x <= 600; x += 10) {
              var option = document.createElement('option');
                  option.value = x;
                if (cSlot.dwdm == x)
                    option.setAttribute("selected", "selected");
                  option.appendChild(document.createTextNode(x));
                select.appendChild(option);
            } li.appendChild(select);
           cancelInputBubble(select);
         optionsUL.appendChild(li);

      } else if (cSlot.name.indexOf("xx") > 0) {
        var li = document.createElement('li');
            li.className = "wdm";
          var h3 = document.createElement('h3');
              h3.appendChild(document.createTextNode("CWDM Wavelength"));
            li.appendChild(h3);
          var select = document.createElement('select');
              select.size = "1";
              select.name = "cwdm";
              select.tabIndex = ti++;
            for (var x = 27; x <= 61; x += 2) {
              /* HACK for 7707VT-8-HS+Cxx & 7708GT-4+Cxx wavelength */
              if ((cSlot.name.indexOf("7707VT-8-HS+") < 0 && cSlot.name.indexOf("7708GT-4+") < 0) || x >= 47) { /* */
                if (x != 39 && x != 41) {
                  var option = document.createElement('option');
                      option.value = x;
                    if (cSlot.cwdm == x) option.setAttribute("selected", "selected");
                      option.appendChild(document.createTextNode(x));
                    select.appendChild(option);
                }
              } /* */
            } li.appendChild(select);
           cancelInputBubble(select);
          optionsUL.appendChild(li);
      }

      if (cSlot.fibers) {
        var li = document.createElement('li');
          var h3 = document.createElement('h3');
              h3.appendChild(document.createTextNode("Fiber Connector"));
            li.appendChild(h3);
          var ul = document.createElement('ul');
            for (var fbr in cSlot.fiber) {
              if (typeof cSlot.fiber[fbr] == "boolean") {
                var li2 = document.createElement('li');
                  var label = document.createElement('label');
                    try {
                      var input = document.createElement('<input type="radio" name="fibers" value="' + fbr + '" tabindex="' + ti + '"' + ((cSlot.fiber[fbr]) ? ' checked="checked"' : '') + ' />');
                    } catch(e) {
                      var input = document.createElement('input');
                          input.type = "radio";
                          input.name = "fibers";
                        if (cSlot.fiber[fbr]) input.checked = "checked";
                          input.value = fbr;
                          input.tabIndex = ti;
                    } label.appendChild(input);
                    cancelInputBubble(input);
                      label.appendChild(document.createTextNode(" +" + fbr));
                    li2.appendChild(label);
                  ul.appendChild(li2);
              }
            } li.appendChild(ul);
          optionsUL.appendChild(li);
      }

      for (var opt in cSlot.option) {
        if (typeof cSlot.option[opt] == "boolean") {
          var li = document.createElement('li');
            var description = optionDescriptions[opt] || "\u2014";
            var label = document.createElement('label');
              try {
                var input = document.createElement('<input type="checkbox" value="' + opt + '" tabindex="' + ti + '"' + ((cSlot.option[opt]) ? ' checked="checked"' : '') + ' />');
              } catch(e) {
                var input = document.createElement('input');
                    input.type = "checkbox";
                  if (cSlot.option[opt]) input.checked = "checked";
                    input.value = opt;
                    input.tabIndex = ti;
              } label.appendChild(input);
              cancelInputBubble(input);
                label.appendChild(document.createTextNode(" +" + opt + ": "));
              if (description) {
                var small = document.createElement('small');
                    small.appendChild(document.createTextNode(description));
                  label.appendChild(small);
              }
              li.appendChild(label);
            optionsUL.appendChild(li);
        } else if (cSlot.option[opt] instanceof Array) {
          var li = document.createElement('li');
            var ul = document.createElement('ul');
                ul.className = "group";
              for (var subopt in cSlot.option[opt]) {
                if (typeof cSlot.option[opt][subopt] == "boolean") {
                  var description = optionDescriptions[subopt] || "\u2014";
                  var li2 = document.createElement('li');
                    var label = document.createElement('label');
                      try {
                        var input = document.createElement('<input type="radio" name="' + opt + '" value="' + subopt + '" tabindex="' + ti + '"' + ((cSlot.option[opt][subopt]) ? ' checked="checked"' : '') + ' />');
                      } catch(e) {
                        var input = document.createElement('input');
                            input.type = "radio";
                            input.name = opt;
                          if (cSlot.option[opt][subopt]) input.checked = "checked";
                            input.value = subopt;
                            input.tabIndex = ti;
                      } label.appendChild(input);
                      cancelInputBubble(input);
                        label.appendChild(document.createTextNode(" " + ((subopt) ? "+" + subopt : "\u2014") + ": "));
                      if (description) {
                        var small = document.createElement('small');
                           small.appendChild(document.createTextNode(description));
                          label.appendChild(small);
                      }
                      li2.appendChild(label);
                    ul.appendChild(li2);
                }
              }
            li.appendChild(ul);
          optionsUL.appendChild(li);
        }
      }

      Pop.options.element.getElementsByTagName('button')[0].tabIndex = ti;
      Pop.options.show();
      Pop.options.element.getElementsByTagName('form')[0].elements[0].focus();

    } else {
      if (Pop.options.element.style.display == "block") {
        Pop.options.hide();

        var inputs = Pop.options.element.getElementsByTagName('input');
        for (var x = 0; x < inputs.length; x++) {
          if (inputs[x].name == "fibers") {
            if (typeof cSlot.fiber[inputs[x].value] == "boolean")
              cSlot.fiber[inputs[x].value] = (inputs[x].checked);
          } else if (inputs[x].name.indexOf("group") === 0) {
            if (cSlot.option[inputs[x].name] && typeof cSlot.option[inputs[x].name][inputs[x].value] == "boolean")
              cSlot.option[inputs[x].name][inputs[x].value] = (inputs[x].checked);
          } else
            if (typeof cSlot.option[inputs[x].value] == "boolean")
              cSlot.option[inputs[x].value] = (inputs[x].checked);
        }

        var wavelength = Pop.options.element.getElementsByTagName('select');
        if (wavelength.length) {
          wavelength = wavelength[0];
          if (wavelength.name == "dwdm") cSlot.dwdm = wavelength.value;
          if (wavelength.name == "cwdm") cSlot.cwdm = wavelength.value;
        }

        if (this.recentCommand) this.recentCommand(true);
        frame[this.working].refresh();
      }
    }
  };


  /* ******************************************************************
   * Raise or lower the IP settings dialogue box
   *
   */
  this.ipDisplay = function() {
    if (this.dragging) return false;
    if (this.view != "edit") this.switchControl('edit');
    if (frame[this.working].loose) {
      alert('Cannot set an IP for a loose collection of modules.');
      return false;
    }

    if (Pop.IPElem.element.style.display == "block") {
      for (var y = 0, octets = []; y < 4; y++) {
        octets[y] = parseInt(document.getElementsByName('ip_address_octet' + y)[0].value);
         octets[y] = (isNaN(octets[y])) ? 0 : Math.max(0, Math.min(255, octets[y]));
       }
      if ((newAdd = octets.join(".")) != "0.0.0.0")
        for (var x = 0, conflict = 0; x < frame.length; x++)
          if (x != this.working && frame[x].isFull() && frame[x].ip['address'] == newAdd) conflict = x + 1;

      if (!conflict || confirm("NOTE: This frame shares its IP with frame #" + conflict + ".  Shared IPs will cause IP conflicts if these frames are used in the same system or network.  Okay to keep this IP?")) {
        this.hideDialogs();

        for (var prop in this.infoips) {
          for (var y = 0, octets = []; y < 4; y++) {
            octets[y] = parseInt(document.getElementsByName('ip_' + prop + "_octet" + y)[0].value);
            octets[y] = (isNaN(octets[y])) ? 0 : Math.max(0, Math.min(255, octets[y]));
          } frame[this.working].ip[prop] = octets.join(".");
        }

        var strong = document.getElementById("f" + (this.working % this.fpp)).getElementsByTagName('strong')[0];
            strong.title = frame[this.working].ip['address'];

        document.getElementById('f' + (this.working % this.fpp)).className = (!frame[this.working].isFull()) ? "empty selected" : "selected";
      } else document.getElementsByName('ip_address_octet3')[0].focus();

    } else {
      this.hideDialogs();

      if (frame[this.working].fc.length && !frame[this.working].fc.find(frame[this.working].slot[0].name)) {
        if (confirm('Enable IP options by placing a Frame Controller in slot #1 ?')) {
          frame[this.working].place(0, modHash[frame[this.working].fc[0]], false);
          frame[this.working].highlight(0, true);
        } else return false;
      }

      Pop.IPElem.element.getElementsByTagName('span')[0].firstChild.nodeValue = this.working + 1;

      for (var prop in this.infoips)
        for (var y = 0, octets = frame[this.working].ip[prop].split("."); y < octets.length; y++)
          document.getElementsByName('ip_' + prop + "_octet" + y)[0].value = octets[y];

      document.getElementById('ip_broadcast').getElementsByTagName('span')[0].firstChild.nodeValue = getBroadcastIP(frame[this.working].ip['address'], frame[this.working].ip['subnetmask']);

      Pop.IPElem.show();
      document.getElementsByName('ip_address_octet0')[0].focus();
    }
  };


  /* ******************************************************************
   * Switch Control Panels
   *
   */
  this.switchControl = function(tab) {
    if (this.mirror) return alert("This function is only available in the online version @ http:\x2f\x2fwww.evertz.com\x2fframe\x2f" + page.frame);
    if (this.dragging) return false;
    this.hideDialogs();

    if (this.view != tab) {
      if (tab != "edit") {
        if (this.view == "edit") {
          this.quoteMessage("Working...", "", "");
          setTimeout(function() { self.sendOutput(false); }, 5);
        } document.getElementById('tabs_' + tab).className = "selected";
      }

      if (this.view != "edit") document.getElementById('tabs_' + this.view).className = "";
      document.getElementById('controls_' + this.view).style.display = "none";
      document.getElementById('controls_' + tab).style.display = "block";

      var legend = document.getElementById('controls').getElementsByTagName('legend')[0];
      switch (tab) {
        case "quote": legend.firstChild.nodeValue = "Send to Agent"; break;
        case "export": legend.firstChild.nodeValue = "Export Images"; break;
        case "save": legend.firstChild.nodeValue = "Save / Recall"; break;
        default: legend.firstChild.nodeValue = "Design Controls"; break;
      }

      this.view = tab;
    }
  };


  /* ******************************************************************
   * Control the Recently Selected Panels list
   *
   */
  this.recentCommand = function(ref) {
    if (ref && frame[this.working].slot[this.cursor].name != "Empty Slot") {
      for (var x = 0, pull = false, mod = frame[this.working].slot[this.cursor].fullName(1); x < this.recent.length; x++)
        if (!pull && mod == this.recent[x].fullName(1)) pull = true;
      if (!pull) {
        this.recent.push(cloneObject(frame[this.working].slot[this.cursor]));
        while (this.recent.length > 16) this.recent.shift();
      }
    }

    // Write Recently Selected List
    var ul = document.getElementById('recent').getElementsByTagName('ul')[0];
    for (var x = 0, lis = ul.getElementsByTagName('li'); x < lis.length; x++) {
      lis[x].span = lis[x].getElementsByTagName('span')[0];
      if (this.recent[x]) {
        var title = this.recent[x].descrip;
        for (var j = 0, k, className = ""; k = extras[j++];) {
          if (this.recent[x].flags[k.code]) {
            className += " " + k.className;
            title += "; " + k.title;
          }
        }

        lis[x].firstChild.firstChild.nodeValue = this.recent[x].fullName(0);
        lis[x].firstChild.className = className;
        lis[x].title = title;
        lis[x].span.className = "slots" + this.recent[x].slots;
        switch (this.recent[x].slots) {
          case 1: lis[x].span.firstChild.nodeValue = "\xa0 Single slot"; break;
          case 2: lis[x].span.firstChild.nodeValue = "\xa0 Double slot"; break;
          case 3: lis[x].span.firstChild.nodeValue = "\xa0 Triple slot"; break;
          case 4: lis[x].span.firstChild.nodeValue = "\xa0 Quadruple slot"; break;
          case 5: lis[x].span.firstChild.nodeValue = "\xa0 Quintuple slot";
        }
        if (!lis[x].me) {
          lis[x].me = x;
          lis[x].className = "";
          lis[x].ondblclick = function(e) {
            if (self.dragging) return false;
            self.hideDialogs();
            if (frame[self.working].place(self.cursor, modHash[self.recent[this.me].name], false)) {
              frame[self.working].slot[self.cursor].subTransfer(self.recent[this.me]);
              self.cursor += frame[self.working].slot[self.cursor].slots;
              frame[self.working].refresh();
            }
          };
          lis[x].onmouseover = function(e) {
            if (self.dragging) return false;
            if (this.className.indexOf('moving') == -1) {
              var img = document.getElementById('recent').getElementsByTagName('img')[0];
                  img.src = self.recent[this.me].fileName(self.recent[this.me].type);
                  img.style.display = "block";
            }
          };
          lis[x].onmouseout = function(e) {
            document.getElementById('recent').getElementsByTagName('img')[0].style.display = "";
          };
          lis[x].onmousedown = panelDragon;
        }
      } else {
        lis[x].firstChild.nodeValue = " ";
        lis[x].span.className = "";
        lis[x].span.firstChild.nodeValue = "";
        lis[x].me = "";
        lis[x].className = "hide";
        lis[x].ondblclick = lis[x].onmouseover = lis[x].onmouseout = lis[x].onmousedown = null;
      }
    }
  };


  /* ******************************************************************
   * Make sure all visible frames are out of label mode
   *
   */
  this.exitLabelMode = function() {
    for (var x = this.page * this.fpp; x < (this.page + 1) * this.fpp; x++)
      if (frame[x] && frame[x].labelMode) frame[x].toggleLabels(true);
  };


  /* ******************************************************************
   * Show the Module Summary
   *
   */
  this.modSummary = function() {
    this.hideDialogs();
    Pop.waiter.show();

    var modName = ["Frames", "Modules", "Rear Panels Only", "Future Purchase - not part of listed POs"];
    for (var modSum = {}, x = 0, enc; enc = enclosures[x++];) {
      modSum[enc.type] = {};
      for (var y = 0; y < modName.length; y++)
        modSum[enc.type][modName[y]] = {};
    }

    for (var x = 0, f; f = frame[x++];) {
      if (f.isFull()) {
        var mfxt = modSum[f.type];
        if (!f.loose) {
          var fnam = f.frame + ((f.hasrps) ? f.rps : "");
          if (!mfxt.Frames[fnam]++) mfxt.Frames[fnam] = 1;
        }
        for (var i = 0, fs; fs = f.slot[i++];) {
          if (fs.name != "Empty Slot") {
            var fnam = fs.fullName(1);
            if (fs.flags.NO) {

            } else if (fs.flags.RP) {
              if (!mfxt[modName[2]][fnam]++) mfxt[modName[2]][fnam] = 1;
            } else if (!mfxt.Modules[fnam]++) mfxt.Modules[fnam] = 1;
          }
        }
      }
    }

    var summaryText = Pop.summary.element.getElementsByTagName('textarea')[0];
        summaryText.value = "";

    for (var prop in modSum) {
      var buffer = prop + " Series:", hasStuff = false;
      for (var x = 0, typ; typ = modName[x++];) {
        var subbuffer = "\n  " + typ + ":\n", dummy = [];
        for (var itm in modSum[prop][typ])
          if (typeof modSum[prop][typ][itm] == "number")
            dummy.push([itm, modSum[prop][typ][itm]]);

        if (dummy.length) {
          hasStuff = true;
          dummy.sort(function(a, b) { return (a[0] == b[0]) ? 0 : ((a[0] > b[0]) ? 1 : -1); });
          for (var y = 0, d; d = dummy[y++];) subbuffer += d[1].toString().lpad(4, " ") + " x  " + d[0] + "\n";
          buffer += subbuffer;
        }
      }
      if (hasStuff) summaryText.value += buffer + "\n\n";
    }
    if (!summaryText.value) summaryText.value = "Empty configuration";
    Pop.waiter.hide();
    Pop.summary.show();
  };


  /* ******************************************************************
   * Blank a file upload input by rewriting it to the page
   *
   */
  this.refreshFileInput = function(box, accept) {
    if (this.mirror) return false;

    var kids = box.getElementsByTagName('input');
    for (var x = 0, old, input; x < kids.length; x++)
      if (kids[x].type == "file") old = kids[x];

    input = document.createElement('input');
    input.type = "file";
    input.name = "upload";
    input.accept = (old) ? old.accept : accept;
    input.size = 13;
    input.onchange = function() {
      if (this.value) {
        Pop.waiter.show();
        clearTimeout(self.uploading);
        self.uploading = setTimeout(function() { alert('Error uploading file!'); }, 15000);
        this.form.submit();
      }
    };
    cancelInputBubble(input);

    if (old) {
      box.replaceChild(input, old);
    } else box.appendChild(input);
  };


  /* ******************************************************************
   * Handle global keyboard input
   * - Return false if key should not repeat if held down
   *
   */
  this.keyCheck = function(keyCode, ctrlKey, shiftKey) {
    if (this.locked) return false;
    if (this.view != "edit") this.switchControl('edit');

    switch (keyCode) {
      case 65: // ******************** CTRL+A
        if (ctrlKey) {
          for (var x = 1; x < frame[this.working].slots; x++) {
            var row = document.getElementById("f" + (this.working % this.fpp) + "s" + x).parentNode;
            if (row.className.indexOf('multiselect') === -1) row.className += " multiselect";
          } this.multimove = -(frame[this.working].slots - 1);
          frame[this.working].highlight(0, false);
        } return false;

      case 67: // ******************** CTRL+C
        if (ctrlKey) this.clip('copy', false);
        return false;

      case 86: // ******************** CTRL+V
        if (ctrlKey) this.clip('paste', false);
        return false;

      case 88: // ******************** CTRL+X
        if (ctrlKey) this.clip('cut', false);
        return false;

      case 46: // ******************** Delete
        this.clip('delete', false);
        return false;

      case 38: // ******************** Up
        if (shiftKey) {
          if (this.multimove <= 0) {
            for (var i = this.cursor; i < this.cursor + frame[this.working].slot[this.cursor].slots; i++) {
              var row = document.getElementById("f" + (this.working % this.fpp) + "s" + i).parentNode;
              if (row.className.indexOf('multiselect') === -1) row.className += " multiselect";
            }
          }
          if (this.cursor > 0) this.multimove--;
        } else {
          for (var i = 0; i < frame[this.working].slots; i++) {
            var row = document.getElementById("f" + (this.working % this.fpp) + "s" + i).parentNode;
            row.className = row.className.removeWord("multiselect");
          } this.multimove = 0;
        }
        frame[this.working].highlight(Math.max(this.cursor - 1,  0), false);
        break;

      case 40: // ******************** Down
        if (shiftKey) {
          if (this.multimove >= 0) {
            for (var i = this.cursor; i < this.cursor + frame[this.working].slot[this.cursor].slots; i++) {
              var row = document.getElementById("f" + (this.working % this.fpp) + "s" + i).parentNode;
              if (row.className.indexOf('multiselect') === -1) row.className += " multiselect";
            }
          }
          if (this.cursor + frame[this.working].slot[this.cursor].slots < frame[this.working].slots) this.multimove++;
        } else {
          for (var i = 0; i < frame[this.working].slots; i++) {
            var row = document.getElementById("f" + (this.working % this.fpp) + "s" + i).parentNode;
            row.className = row.className.removeWord("multiselect");
          } this.multimove = 0;
        }
        frame[this.working].highlight(Math.min(this.cursor + frame[this.working].slot[this.cursor].slots, frame[this.working].slots - 1), false);
        break;

      case 39: // ******************** Right
        frame[Math.min(this.working + 1, this.fpp * this.pages - 1)].highlight(this.cursor, true);
        if (this.working % this.fpp == 0) this.switchPage(Math.min(this.page + 1, this.pages - 1));
        break;

      case 37: // ******************** Left
        frame[Math.max(this.working - 1, 0)].highlight(this.cursor, true);
        if (this.working % this.fpp == 5) this.switchPage(Math.max(this.page - 1, 0));
        break;

      case 34: // ******************** Page Down
        this.switchPage(Math.min(this.page + 1, this.pages - 1));
        break;

      case 33: // ******************** Page Up
        this.switchPage(Math.max(this.page - 1, 0));
        break;

      case 35:
      case 36: // ******************** End, Home
        if (shiftKey) {
          for (var i = this.cursor; i < frame[this.working].slots && i >= 0;) {
            var row = document.getElementById("f" + (this.working % this.fpp) + "s" + i).parentNode;
            if (row.className.indexOf('multiselect') === -1) row.className += " multiselect";
            if (keyCode == 35) {
              if (i++ != this.cursor) this.multimove++;
            } else if (i-- != this.cursor) this.multimove--;
          }
        }
        frame[this.working].highlight((keyCode == 35) ? frame[this.working].slots - 1 : 0, !shiftKey);
        return false;

      case 13: // ******************** Enter
        if (Pop.selector.element.style.display != "block" &&
            Pop.options.element.style.display != "block") {
          this.exitLabelMode();
          this.selectorDisplay(true);
        } return false;

      case 27: // ******************** ESC
        this.hideDialogs();
        return false;

      default: return false;
    } return true;
  };


  /* ******************************************************************
   * Read and add information from a recalled configuration
   *
   */
  this.readFile = function() {
    clearTimeout(this.uploading);

    for (var x = 0, full = 0; x < frame.length; x++) if (frame[x].isFull()) full++;

    var frms, doc = document.getElementsByTagName('iframe')[0].contentWindow.document;
    if (doc.getElementsByTagName('h1').length) {

      for (var x = 0, fd = 0, ex = false, info = [], elem; x < this.infofields.length; x++) {
        if ((elem = doc.getElementById('info_' + this.infofields[x])) && elem.firstChild) {
          info[this.infofields[x]] = elem.firstChild.nodeValue;
          if (document.getElementsByName('info_' + this.infofields[x])[0].value) ex = true;
          fd++;
        }
      }
      for (var x = 0; x < this.infomultis.length; x++) {
        if ((elem = doc.getElementById('info_' + this.infomultis[x])) && elem.firstChild) {
          info[this.infomultis[x]] = elem.firstChild.nodeValue;
          var imult = document.getElementsByName('info_' + this.infomultis[x]);
          for (var y = 0; y < imult.length; y++) if (imult[y].value) ex = true;
          fd++;
        }
      }

      if (!ex || (fd && confirm('Overwrite existing Customer Information?'))) {
        var infoproj = document.getElementById('information_project');
        var labels = infoproj.getElementsByTagName('label');
        for (var x = labels.length - 1; x >= 2; x--) infoproj.removeChild(labels[x]);

        for (var x = 0, peRows = 1; x < this.infofields.length; x++) {
          if (info[this.infofields[x]]) document.getElementsByName('info_' + this.infofields[x])[0].value = info[this.infofields[x]];
          if (info[this.infomultis[x]]) {
            for (var y = 0, inf = info[this.infomultis[x]].split(","); y < inf.length; y++) {
              if (y >= peRows) {
                peRows++;
                this.poesoRow(0);
              }
              document.getElementsByName('info_' + this.infomultis[x])[y].value = inf[y].trim();
            }
          }
        }
      }

      if (document.getElementsByName('info_agent')[0].selectedIndex == -1)
        document.getElementsByName('info_agent')[0].selectedIndex = 0;
      document.getElementsByTagName('h2')[0].firstChild.nodeValue = document.getElementsByName('info_project')[0].value;

      if ((frms = doc.getElementsByTagName('div')).length) {
        for (var x = 0, start = this.flimit, count = 0; x < this.flimit && count < frms.length; x++) {
          if (!frame[x]) this.addFrames(true);
          if (!frame[x].isFull()) {
            count++; if (start == this.flimit) start = x;
          } else { count = 0; start = this.flimit; }
        }

        for (var x = 0, y = start; y < this.flimit && x < frms.length; x++, y++) {

          // ***** Frame ID = <h2>
          try { frame[y].frameid = frms[x].getElementsByTagName('h2')[0].firstChild.nodeValue.trim(); } catch(e) { frame[y].frameid = parseInt(frame[y].id) + 1; }

          // ***** Frame = <h3>
          var newframe = "7800FR";
          try { newframe = frms[x].getElementsByTagName('h3')[0].firstChild.nodeValue.trim(); } catch(e) {}
          if (frame[y].frame != newframe) frame[y].switchType(newframe);

          // ***** Slots = <ul><li>
          for (var z = 0, slt = frms[x].getElementsByTagName('ul')[0].getElementsByTagName('li'); z < slt.length; z++) {
            if (slt[z].firstChild) {
              // Turn an old value into a new value:
              var modStr = slt[z].firstChild.nodeValue.trim();
              if (modStr.indexOf("+RP") > -1)
                modStr = frame[y].type + "P(" + modStr.replace(/\+RP/, '') + ")";
              if (modStr.indexOf("+NO") > -1) modStr += " NotOrdered";

              // Recall using new save syntax
              var mod = makeModule(modStr);
              frame[y].place(z, modHash[mod.name], true);
              frame[y].slot[z].subTransfer(mod);
            }
          }

          // ***** PS2 = <var>
          try { frame[y].hasrps = (frms[x].getElementsByTagName('var')[0].firstChild.nodeValue.trim() == "Yes") ? true : false; } catch(e) { frame[y].hasrps = false; }

          // ***** Old IP method = <h3>
          try { frame[y].ip.address = frms[x].getElementsByTagName('h3')[0].firstChild.nodeValue.trim(); } catch(e) { frame[y].ip.address = "0.0.0.0"; }

          // ***** New IPs method = <ol><li>
          var i = 0, ips = frms[x].getElementsByTagName('ol')[0].getElementsByTagName('li');
          for (var prop in this.infoips)
            try { frame[y].ip[prop] = ips[i++].firstChild.nodeValue.trim(); } catch(e) { frame[y].ip[prop] = this.infoips[prop]; }
        }

        this.switchPage(this.page++);
        Pop.waiter.hide();

        if (x == 0) {
          alert('No frames could be added.');
        } else if (x < frms.length) {
          alert('There was not enough room to add all recalled frames.  The first ' + x + ' of ' + frms.length + ' recalled frames were added, starting at frame #' + (start + 1));
        } else {
          Taint = (full);
          alert(frms.length + ' recalled frames were added, starting at frame #' + (start + 1));
        }
      } else alert('No frames found in this file');
    } else {
      var err = doc.getElementsByTagName('h3');
      if (err.length) alert(err[0].firstChild.nodeValue);
    }
    this.refreshFileInput(document.getElementById('upload_config'), "");
    Pop.waiter.hide();
  };


  /* ******************************************************************
   * Read an incoming Panel Palette
   *
   */
  this.readPalette = function() {
    clearTimeout(this.uploading);

    var p, doc = document.getElementsByTagName('iframe')[0].contentWindow.document;
    if ((p = doc.getElementsByTagName('p')).length) {
      this.recent = [];
      for (var x = 0, y = 0; x < p.length; x++) {
        if (p[x].firstChild) {

          // Turn an old value into a new value:
          var modStr = p[x].firstChild.nodeValue.trim();
          if (modStr.indexOf("+RP") > -1)
            modStr = frame[y].type + "P(" + modStr.replace(/\+RP/, '') + ")";
          if (modStr.indexOf("+NO") > -1) modStr += " NotOrdered";

          // Recall using new save syntax
          var mod = makeModule(modStr);
          if (mod.name != "Empty Slot") this.recent[y++] = mod;
        }
      }
      this.recentCommand(false);
    }
    this.refreshFileInput(document.getElementById('upload_palette'), "");
    Pop.waiter.hide();
  };


  /* ******************************************************************
   * Gather form information and send via AJAX
   *
   */
  this.sendOutput = function(preview) {
    if (this.mirror) return alert("This function is only available in the online version @ http:\x2f\x2fwww.evertz.com\x2fframe\x2f" + page.frame);
    if (preview && !frame[this.working].isFull()) return alert("Frame is empty");

    var link = document.getElementById('link_send');
    link.disabled = "disabled";
    link.onclick = null;
    Pop.waiter.show();

    for (var x = 0, values = ["frame=" + page.frame]; x < this.infofields.length; x++)
      if (document.getElementsByName('info_' + this.infofields[x])[0])
        values.push("info_" + this.infofields[x] + "="  + encodeURIComponent(document.getElementsByName('info_' + this.infofields[x])[0].value));

    for (var x = 0; x < this.infomultis.length; x++) {
      for (var y = 0, val = [], mult = document.getElementsByName('info_' + this.infomultis[x]); y < mult.length; y++)
        if (mult[y].value) val.push(mult[y].value);
      values.push("info_" + this.infomultis[x] + "="  + encodeURIComponent(val.join(", ")));
    }

    for (var x = 0, pfx; x < frame.length; x++) {
      if (frame[x].isFull()) {
        pfx = (this.type == "Bulk") ? "f" + x : "";
        values.push(pfx + "id=" + encodeURIComponent(frame[x].frameid));
        values.push(pfx + "rps=" + ((frame[x].hasrps) ? "Yes" : "No"));
        values.push(pfx + "rfc=" + ((frame[x].hasrfc) ? "Yes" : "No"));
        if (this.type == "Bulk") {
          values.push(pfx + "frame=" + encodeURIComponent(frame[x].frame));
          for (var prop in this.infoips)
            values.push(pfx + "ip_" + prop + "=" + frame[x].ip[prop]);
        }

        for (var y = 0; y < frame[x].slots; y++) {
          if (!frame[x].slot[y].multi && frame[x].slot[y].name != "Empty Slot") {
            values.push(pfx + "module" + y + "=" + encodeURIComponent(frame[x].slot[y].fullName(2)));
            values.push(pfx + "image" + y + "=" + encodeURIComponent(frame[x].slot[y].fileName(0, frame[x].rotate)));
          }
        }
      }
    }

    var http = getHTTPObject();
    http.open("POST", "store", true);
    http.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    http.onreadystatechange = function() {
      if (http.readyState == 4) {
        self.md5 = http.responseXML.getElementsByTagName('md5');
        var err = http.responseXML.getElementsByTagName('error');

        if (self.md5.length) {
          var sent = http.responseXML.getElementsByTagName('sent')[0].firstChild.nodeValue;
          self.md5 = self.md5[0].firstChild.nodeValue;

          if (document.getElementById('tabs_quote')) {
            if (sent == "no") {
              for (var x = 0, full = 0; x < frame.length; x++) if (frame[x].isFull()) full++;
              if (full) {
                if (document.getElementsByName('info_agent')[0].value != "" &&
                    document.getElementsByName('info_contact')[0].value) {
                  link.disabled = "";
                  link.onclick = function() { self.getQuote(); };

                  self.quoteMessage("Ready to Send", "Click the \"Send\" button to send this " + self.wname + " to " + document.getElementsByName('info_agent')[0].options[document.getElementsByName('info_agent')[0].selectedIndex].text + ".", "");
                } else self.quoteMessage("Incomplete", "To send your " + self.wname + ", please select a Sales Agent and include your Contact Information.", "");
              } else self.quoteMessage("Nothing to Send", "Please select your desired modules before sending a " + self.wname + ".", "");
            } else self.quoteMessage("Already Sent", "This " + self.wname + " has already been sent. To send multiple, similar " + self.wname + "s, use different Project Names for each.", "");
          }

          if (preview) {

            // Firefox hack to force reset of image.complete
            var img = document.createElement('img');
            img.src = "frame?frame=" + page.frame + "&id=" + self.md5 + ((self.type == "Bulk") ? "&offset=" + self.working + "&width=920" : "");
            img.alt = "Completed " + frame[self.working].name;
            document.getElementById('frameimage').parentNode.replaceChild(img, document.getElementById('frameimage'));
            img.id = "frameimage";

            img.wait = setInterval(function() { (function() {
              if (this.complete && (!this.readyState || this.readyState == "complete")) {
                Pop.waiter.hide();
                if (Pop.framePreview) {
                  Pop.framePreview.element.getElementsByTagName('span')[0].firstChild.nodeValue = frame[self.working].frameid;
                  Pop.framePreview.show();
                } else {
                  this.onclick = function() { self.switchControl('edit'); };
                  this.style.visibility = "visible";
                } clearInterval(this.wait);
              }
            }).call(img) }, 200);
          } else Pop.waiter.hide();

        } else if (err.length) {
          self.quoteMessage("ID Error", "There was an error finding the " + self.wname + " ID.  Please try again.", "");
          alert(err[0].firstChild.nodeValue);
          Pop.waiter.hide();
        }
      }
    };
    http.send(values.join("&"));
    return true;
  };


  /* ******************************************************************
   * Change the Get Quote interface text
   *
   */
  this.quoteMessage = function(title, message, className) {
    var inter = document.getElementById('interface');
    while (inter.firstChild) inter.removeChild(inter.firstChild);
        inter.className = className;
      var h2 = document.createElement('h2');
          h2.appendChild(document.createTextNode(title));
        inter.appendChild(h2);
      var p = document.createElement('p');
          p.appendChild(document.createTextNode(message));
        inter.appendChild(p);
  };


  /* ******************************************************************
   * Send this frame ID via AJAX to get a quote
   *
   */
  this.getQuote = function() {
    document.getElementById('link_send').disabled = "disabled";
    Pop.waiter.show();

    var http = getHTTPObject();
    http.open("GET", "/includes/ajax/framesend?type=" + this.type + "&id=" + this.md5, true);
    http.onreadystatechange = function() {
      if (http.readyState == 4) {
        switch (http.responseText) {
          case "Success": self.quoteMessage("Thank you!", "This " + page.name + " " + self.wname + " has been sent to your selected sales agent.", "complete"); break;
          case "Invalid": self.quoteMessage("Error!", "There was a problem in transferring the data to be mailed. Please go back to the editing page and try again.", "error"); break;
          case "AlreadySent": self.quoteMessage("Already Sent", "This " + self.wname + " and the associated information has already been mailed to your selected sales agent.", "error"); break;
          case "NotFound": self.quoteMessage("I'm sorry!", "I could not find any completed " + self.wname + "s in the database which match yours; it may have expired.", "error"); break;
          case "Fail": default: self.quoteMessage("Error!", "I'm sorry, but it seems the message could not be sent. Please go back to the editing page and try again.", "error"); break;
        }
        Pop.waiter.hide();
      }
    };
    http.send(null);
  };


  /* ******************************************************************
   * Bookmark this configuration
   *
   */
  this.getBookmark = function() {
    var http = getHTTPObject();
    http.open("GET", "/frame/saverecall?type=" + page.type + "&method=bookmark&id=" + this.md5, true);
    http.onreadystatechange = function() {
      if (http.readyState == 4) {
        if (http.responseText == "Saved") {
          Taint = false;
          var savedlink = Pop.savedNotice.element.getElementsByTagName('a')[0];
              savedlink.href = savedlink.getElementsByTagName('span')[0].firstChild.nodeValue = page.frame + "?id=" + self.md5;
            Pop.savedNotice.show();
        } else alert(http.responseText);
      }
    };
    http.send(null);
    return false;
  };


  /* ******************************************************************
   * Find the block diagram link of a parent product
   *
   */
  this.findBlock = function(parentProd) {
    var http = getHTTPObject();
    http.open("HEAD", "/products/block/" + parentProd.replace(/\//g, ".") + ".pdf?" + (new Date()).getTime(), true);
    http.onreadystatechange = function() {
      if (http.readyState == 4) {
        var scbd = document.getElementById('scBlockDiagram');
        var slcbd = document.getElementById('slcBlockDiagram');
        scbd.className = slcbd.className = (http.status == 200) ? "" : "disabled";
        scbd.getElementsByTagName('a')[0].href = slcbd.getElementsByTagName('a')[0].href = "../products/block/" + parentProd.replace(/\//g, ".") + ".pdf";
      }
    };
    http.send(null);
  };


  /* ******************************************************************
   * Request data export from the server
   *
   */
  this.exportData = function(type) {
    if (this.dragging) return false;

    var remTaint = Taint;

    for (var x = 0, full = 0; x < frame.length; x++) if (frame[x].isFull()) full++;
    if (this.full = full) {
      if (type == "csv") {
        Taint = false;
        window.location.href = "saverecall?method=save&id=" + this.md5;
        setTimeout((function(remTaint) { return function() { Taint = remTaint; }})(remTaint), 5);
        return false;
      }
      if (type == "dxf" && !confirm("Exporting DXF images may take several minutes to complete.  Continue?")) return false;

      var prog = document.getElementById('progress');
      var progs = prog.getElementsByTagName('span');
      for (var x = 0; x < progs.length; x++) progs[x].className = "on";
      prog.style.display = "block";
      Pop.waiter.show();

      var http = getHTTPObject(), check = getHTTPObject(), interval;
      http.open("GET", "/frame/packager?type=Bulk&method=" + type + "&id=" + this.md5, true);
      http.send(null);

      interval = setInterval(function() {
        check.abort();
        check.open("GET", "/frame/temp/" + self.md5 + "/progress.txt?" + (new Date()).getTime(), true);
        check.onreadystatechange = function() {
          if (check.readyState == 4 && check.status == 200) {
            if (check.responseText.indexOf("Error: ") === 0) {
              Pop.waiter.hide();
              prog.style.display = "";
              clearInterval(interval);

            } else if (check.responseText.indexOf("Done: ") === 0) {
              for (var x = 0; x < progs.length; x++) progs[x].className = "on done";
              setTimeout(function() {
                (function(url) {
                  Pop.waiter.hide();
                  prog.style.display = "";
                  if (isIE) {
                    Pop.fileComplete.element.getElementsByTagName('a')[0].href = url;
                    Pop.fileComplete.show();
                  } else {
                    Taint = false;
                    window.location.href = url;
                    setTimeout((function(remTaint) { return function() { Taint = remTaint; }})(remTaint), 5);
                  }
                })("/frame/temp/" + self.md5 + "/" + check.responseText.substr(6) + "?" + (new Date()).getTime())
              }, 500);
              clearInterval(interval);

            } else {
              var comp = Math.ceil((parseInt(check.responseText) / self.full) * progs.length);
              for (var x = 0; x < comp && comp <= progs.length; x++) progs[x].className = "on done";
            }
          }
        };
        check.send(null);
      }, 500);
    } else alert("Configuration is empty");
    return false;
  };
}



/* ***************************************************************************************************************************
 * Frame object
 *
 */
function Frame(id, ftype) {
  var self = this;

  this.id        = id;
  this.frameid   = (id + 1) + "";
  this.ip        = cloneObject(app.infoips);
  this.frame     = "";
  this.type      = "";
  this.loose     = false;
  this.ps        = "";
  this.rps       = "";
  this.hasrps    = false;
  this.fc        = [];
  this.rfc       = false;
  this.hasrfc    = false;
  this.split     = [];
  this.reverse   = false;
  this.slots     = 0;
  this.slot      = [];
  this.newmodule = false;

  this.labelMode = false;
  this.image     = [];


  /* ******************************************************************
   * Set properties associated with a certain frame type
   *
   */
  this.setType = function(ftype) {
    var retyped = false;
    if (ftype == "Bulk") ftype = "7800FR";
    for (var x = 0; x < enclosures.length; x++) {
      if (ftype == enclosures[x].frame || ftype == enclosures[x].type) {
        if (this.type != enclosures[x].type) retyped = true;
        for (var prop in enclosures[x]) this[prop] = cloneObject(enclosures[x][prop]);
      }
    }
    if (retyped) {
      this.slot = [];
      for (var i = 0; i < this.slots; i++) this.slot[i] = cloneObject(modules[0]);
    } else if (this.slots > this.slot.length) {
      for (var i = this.slot.length; i < this.slots; i++) this.slot[i] = cloneObject(modules[0]);
    } else if (this.slots < this.slot.length) {
      var cutoff = this.slots;
      while (this.slot[cutoff].multi) cutoff--;
      this.place(cutoff, 0, true);
      for (var i = this.slot.length - 1; i >= this.slots; i--) this.slot.pop();
    }
  };
  this.setType(ftype);


  /* ******************************************************************
   * Change the frame model of this frame
   *
   */
  this.switchType = function(newType) {
    if (newType != this.frame && encHash[newType] !== undefined) {
      app.hideDialogs();

      if (enclosures[encHash[newType]].type == enclosures[encHash[this.frame]].type) {
        for (var x = 0, conflict = []; x < this.slots; x++) {
          if (!modules[modHash[this.slot[x].name]].allowed(enclosures[encHash[newType]].type, newType, x)) conflict.push(x);
          x += this.slot[x].slots - 1;
        }
        if (conflict.length) {
          if (confirm('Some or all of your currently selected modules cannot be placed in the ' + newType + ' frame.  Do you want to remove these modules and proceed anyway?')) {
            for (var x = 0; x < conflict.length; x++)
              this.place(conflict[x], 0, true);
          } else return false;
        }

        for (var x = enclosures[encHash[newType]].slots, lost = false; x < this.slots; x++)
          if (this.slot[x].multi || this.slot[x].name != "Empty Slot") lost = true;
        if (lost && !confirm('The frame you have selected has fewer slots than the currently selected frame; switching will cause some selected modules to be lost.  Continue anyway?')) return false;
      } else if ((this.isFull() & 4) && !confirm('Choosing a new frame type will clear all modules of the previous frame type.  Okay to change frame type?')) return false;

      this.setType(newType);
      this.refresh();
    } else alert("Not a valid frame model!");
  };


  /* ******************************************************************
   * Highlight a selected module
   *
   */
  this.highlight = function(slot, wipe) {
    if (app.dragging) return false;
    if (app.view != "edit") app.switchControl('edit');

    var reduc = app.working % app.fpp;
    if (app.working != this.id) app.exitLabelMode();

    for (var i = 0, empty = true; i < frame[app.working].slots; i++) {
      var row = document.getElementById("f" + reduc + "s" + i).parentNode;
      row.className = row.className.removeWord("selected");
      if (wipe || app.working != this.id) row.className = row.className.removeWord("multiselect");
    }
    document.getElementById('f' + reduc).className = (!frame[app.working].isFull()) ? "empty" : "";

    app.working = this.id;
    if (wipe) app.multimove = 0;
    slot = Math.min(this.slots - 1, slot);
    while (this.slot[slot].multi) slot--;

    if (slot != app.cursor) {
      app.hideDialogs();
      app.cursor = slot;
    }

    for (var i = slot; i < slot + this.slot[slot].slots; i++)
      document.getElementById("f" + (this.id % app.fpp) + "s" + i).parentNode.className = "selected";

    var classes = ["selected"];
    if (!this.isFull()) classes.push("empty");
    if (this.labelMode) classes.push("labels");
    document.getElementById('f' + (this.id % app.fpp)).className = classes.join(" ");

    if (this.newmodule && this.slot[app.cursor].anyopt) {
      app.optionsDisplay(true);
    } else app.recentCommand(true);

    if (document.activeElement && document.activeElement != document.body)
      try { document.activeElement.blur(); } catch(e) {}
    this.newmodule = false;
    return app.cursor;
  };


  /* ******************************************************************
   * Refresh the entire frame
   *
   */
  this.refresh = function() {
    if (Math.floor(this.id / 6) != app.page) return false;
    var reduc = this.id % app.fpp;

    document.getElementById("f" + reduc).getElementsByTagName('em')[0].firstChild.nodeValue = (this.id + 1);
    document.getElementById("f" + reduc + "id").value = this.frameid;

    for (var i = 0, power = 10, modu; i < this.slots; i++) {
      this.slot[i].multi = false;
      var title = this.slot[i].descrip;

      if (this.slot[i].name == "Future-Use") this.slot[i].flags.NO = true;

      for (var j = 0, k, className = ""; k = extras[j++];) {
        if (this.slot[i].flags[k.code]) {
          className += " " + k.className;
          if (this.slot[i].name != "Future-Use") title += "; " + k.title;
        }
      }

      modu = document.getElementById("f" + reduc + "s" + i);
      modu.firstChild.replaceChild(document.createTextNode(this.slot[i].fullName(0)), modu.firstChild.firstChild);
      modu.parentNode.className = "";

      modu.title = (title != "Empty Slot") ? title + ((this.slot[i].label) ? " (" + this.slot[i].label + ")" : "") : "";
      modu.className = className;
      for (var j = i + 1, modj; j < i + this.slot[i].slots; j++) {
        this.slot[j].multi = true;
        modj = document.getElementById("f" + reduc + "s" + j);
        modj.firstChild.innerHTML = " &nbsp; &ndash;";
        modj.parentNode.className = "";
        modj.className = className;
        modj.title = (title != "Empty Slot") ? title + ((this.slot[i].label) ? " (" + this.slot[i].label + ")" : "") : "";
      } i = j - 1;
    }
    for (var i = this.slots; i < 16; i++) {
      var slot = document.getElementById("f" + reduc + "s" + i);
      if (slot.parentNode.className != "closed") {
          slot.firstChild.replaceChild(document.createTextNode("\xa0"), slot.firstChild.firstChild);
          slot.parentNode.className = "closed";
          slot.title = "Not available";
      }
    }

    var className = (!this.isFull()) ? "empty" : "";
    if (this.id == app.working) className += " selected";
    document.getElementById('f' + (this.id % app.fpp)).className = className.trim();
    document.getElementById("f" + reduc).tFoot.getElementsByTagName('div')[0].firstChild.nodeValue = this.frame;


    var fcpres = (this.fc.find(this.slot[0].name));
    var strong = document.getElementById("f" + reduc).getElementsByTagName('strong')[0];
        strong.className = (fcpres) ? "" : "disabled";
        strong.title = (fcpres) ? this.ip['address'] : "Frame requires a Frame Controller";

    this.setrps((this.loose) ? false : this.hasrps);
    if (this.id == app.working) this.highlight(app.cursor, true);
  };


  /* ******************************************************************
   * Place a module into the frame
   *
   */
  this.place = function(slot, modNum, silent) {
    app.hideDialogs();
    var slottype = (this.image.length) ? this.image[slot].ptype : this.type;
    if (!modules[modNum].allowed(slottype, this.frame, slot) || this.slot[slot].multi) return false;

    for (var i = slot + 1; i < slot + modules[modNum].slots; i++) {
      if (i >= this.slots || this.split.find(i) || this.slot[i].name != "Empty Slot") {
        if (slot == 0 || this.split.find(slot) || this.slot[slot - 1].multi || this.slot[slot - 1].name != "Empty Slot") {
          if (!silent) alert("There is not enough room to place this module here.\nThis module requires " + modules[modNum].slots + " available slots.");
          this.newmodule = false;
          return false;
        } else i = --slot + 1;
      }
    }

    // Remove existing module
    for (var i = slot, end = this.slot[slot].slots; i < slot + end; i++)
      this.slot[i] = cloneObject(modules[0]);

    if (modNum) {
      // Paste new module
      this.slot[slot] = cloneObject(modules[modNum]);
      for (var x = slot + 1; x < slot + this.slot[slot].slots; x++) this.slot[x].multi = true;
      if (this.slot[slot].name == "Future-Use") this.slot[slot].flags.NO = true;

      if (!silent) {
        app.cursor = slot;
        this.refresh();
        if (slot == 0 && modules[modNum].name != "Empty Slot" && this.fc.length && !this.fc.find(modules[modNum].name))
          setTimeout(function() { self.fcswitch(); }, 10);
      }
    } else if (!silent) this.refresh();
    return Taint = true;
  };


  /* ******************************************************************
   * Warn user about having a Frame Controller in slot #1
   *
   */
  this.fcswitch = function() {
    if (confirm("To monitor and/or control this frame using VistaLINK PRO or a 9000NCP Control Panel, slot one must contain a Frame Controller module.\n\nReplace the current module in slot one with a " + this.fc[0] + "?")) {
      this.place(0, modHash[this.fc[0]], false);
      this.highlight(0, true);
    }
  };


  /* ******************************************************************
   * Check whether this frame is full
   *  - Returns a binary value telling exactly what has been set
   *    - 001 => Has a modified Frame ID
   *    - 010 => Has a Redundant PS or Redundant FC set
   *    - 100 => Has at least one module
   *
   */
  this.isFull = function() {
    for (var i = 0, full = 0; i < this.slots; i++) if (this.slot[i].name != "Empty Slot") { full += 4; break; }
    if (this.hasrps || this.hasrfc) full += 2;
    if (this.frameid.length && this.frameid != (this.id + 1).toString()) full += 1;
    return full;
  };


  /* ******************************************************************
   * Clear all modules from the frame
   *
   */
  this.clear = function() {
    if (app.type == "Bulk") app.exitLabelMode();
    if (this.isFull() && confirm('Really clear this frame?')) {
      for (var i = 0; i < this.slots; i++) this.slot[i] = cloneObject(modules[0]);
      this.setrps(false);
      this.frameid = (this.id + 1) + "";
      this.ip = cloneObject(app.infoips);
      this.refresh();
    }
  };


  /* ******************************************************************
   * Toggle redundant power supply
   *
   */
  this.setrps = function(chk) {
    app.exitLabelMode();
    if (app.view != "edit") app.switchControl('edit');
    app.hideDialogs();

    var rpstog = document.getElementById('f' + (this.id % app.fpp) + 'rpstog');
    if (this.loose || !this.rps) {
      this.hasrps = false;
      rpstog.src = "img/check-disabled.png";
      rpstog.alt = "No";
    } else {
      Taint = true;

      rpstog.src = (this.hasrps = chk) ? "img/check-on.png" : "img/check-off.png";
      rpstog.alt = (chk) ? "Yes" : "No";
    }

    var className = (!this.isFull()) ? ["empty"] : [];
    if (this.id == app.working) className.push("selected");
    document.getElementById('f' + (this.id % app.fpp)).className = className.join(" ");
  };


  /* ******************************************************************
   * Toggle the label application mode for this frame
   *
   */
  this.toggleLabels = function(silent) {
    if (!silent) this.highlight(app.cursor, true);
    var reduc = this.id % app.fpp;
    var tab = document.getElementById('f' + reduc);
    var cell = tab.tFoot.getElementsByTagName('td')[0];

    if (this.labelMode ^= 1) {
      tab.className += " labels";
      cell.firstChild.firstChild.nodeValue = "Modules";

      for (var i = 0, slot, input; i < this.slots; i++) {
        input = document.createElement('input');
        input.type = "text";
        input.slot = this.slot[i];
        input.maxLength = 32;
        input.value = this.slot[i].label || "No label";
        if (!this.slot[i].label) input.className = "fade";
        cancelInputBubble(input);
        input.onclick = function(e) {
          return !((e || event).cancelBubble = true);
        };
        input.onfocus = function() {
          if (this.value == "No label") this.value = "";
          this.className = "";
          return true;
        };
        input.onblur = function() {
          if (this.value == "") {
            this.value = "No label";
            this.className = "fade";
          }
        };
        slot = document.getElementById("f" + reduc + "s" + i);
        slot.title = input.title = slot.firstChild.nodeValue;
        slot.firstChild.replaceChild(input, slot.firstChild.firstChild);
        try { i += (this.slot[i].slots - 1); } catch(e) { alert(i); }
      }
    } else {
      cell.firstChild.firstChild.nodeValue = "Labels";
      for (var i = 0; i < this.slots; i++) {
        var slot = document.getElementById("f" + reduc + "s" + i);
        if (slot.firstChild.firstChild.nodeName == "INPUT") {
          if (slot.firstChild.firstChild.value == "No label") slot.firstChild.firstChild.value = "";
          if (this.slot[i].name == "Empty Slot" && slot.firstChild.firstChild.value) this.place(i, 1, true);
          this.slot[i].label = slot.firstChild.firstChild.value;
        }
      } this.refresh();
    }
  };
}


/* ***************************************************************************************************************************
 * Handles drag'n of the Selected and Recent Panels
 *
 */
function panelDragon(e) {
  if (app.dragging) return false;

  var self = this, e = e || event;
  this.posn = [Number(this.style.left.replace(/px/, "")) - e.clientX, Number(this.style.top.replace(/px/, "")) - e.clientY];
  this.pid = false;
  this.modu = (this.me || this.me === 0) ? app.recent[this.me] : frame[app.working].slot[app.cursor];
  document.documentElement.onmousemove = function(e) {
    e = e || event;
    if (!app.dragging) {
      app.hideDialogs();
      self.className += " moving";
      self.hidetitle = self.title;
      self.title = "";
      document.getElementById('recent').getElementsByTagName('img')[0].style.display = "";
    } app.dragging = true;
    self.style.left = self.posn[0] + e.clientX + "px";
    self.style.top = self.posn[1] + e.clientY + "px";
    var match, scrld = scrollDist(), below = dragon.getZoneFromPoint(e.clientX + scrld[0], e.clientY + scrld[1]);
    if (below != self.pid) {
      if (typeof self.pid == "string" && self.pid) {
        var prev = document.getElementById(self.pid).parentNode;
        if (prev.className != "closed")
          prev.className = prev.className.removeWord("hover");
      }
      if (typeof below == "string" && (match = app.trpattern.exec(below))) {
        var ftar = parseInt(match[1]) + app.page * app.fpp;
        if (!(frame[ftar].isFull() & 4) || modules[modHash[self.modu.name]].allowed(frame[ftar].type, frame[ftar].frame, parseInt(match[2]))) {
          var next = document.getElementById(below).parentNode;
          if (next.className != "closed") next.className += " hover";
        }
      } self.pid = below;
    } e.cancelBubble = true;
    return false;
  };
  document.documentElement.onmouseup = function(e) {
    if (app.dragging) {
      app.dragging = false;
      self.style.left = self.style.top = "0px";
      self.className = self.className.removeWord("moving");
      self.title = self.hidetitle;
      if (typeof self.pid == "string" && self.pid) {
        var match, prev = document.getElementById(self.pid).parentNode;
        if (prev.className != "closed") {
          prev.className = prev.className.removeWord("hover");
          if (match = app.trpattern.exec(self.pid)) {
            var ftar = parseInt(match[1]) + app.page * app.fpp, star = parseInt(match[2]);
            if (!isNaN(ftar) && !isNaN(star)) {
              if (!modules[modHash[self.modu.name]].frames.find(frame[ftar].frame) && !(frame[ftar].isFull() & 4)) {
                frame[ftar].switchType(modules[modHash[self.modu.name]].frames[0]);
                star = Math.min(star, frame[ftar].slots - modules[modHash[self.modu.name]].slots);
              }
              if (frame[ftar].place(star, modHash[self.modu.name], true)) {
                frame[ftar].slot[star].subTransfer(self.modu);
                frame[ftar].refresh();
              }
            }
          }
        }
      }
    }
    document.documentElement.onmousemove = null;
    document.documentElement.onmouseup = null;
  };
  return false;
}


/* ***************************************************************************************************************************
 * Handles drag'n of slots
 *
 */
function slotDragon(e) {
  if (app.dragging) return false;
  document.documentElement.onmousemove = null;
  document.documentElement.onmouseup = null;

  var self = this, e = e || event, match;
  if (match = app.trpattern.exec(this.cells[1].id)) {
    this.frame = parseInt(match[1]) + app.page * app.fpp;
    this.slot = parseInt(match[2]);
    if (frame[this.frame].labelMode) return false;
    if (frame[this.frame].slot[this.slot] && (frame[this.frame].slot[this.slot].multi || frame[this.frame].slot[this.slot].name != "Empty Slot")) {
      while (frame[this.frame].slot[this.slot].multi) this.slot--;
      this.slots = frame[this.frame].slot[this.slot].slots;
      this.select = (this.frame == app.working && this.slot == app.cursor);
      this.rows = [];
      var rowPos = findPos(document.getElementById('f' + (this.frame % 6) + 's' + this.slot).parentNode);
      var parPos = findPos(document.getElementById('mainlayout'));
      this.posn = [rowPos[0] - parPos[0] + 1 - e.clientX, rowPos[1] - parPos[1] + 1 - e.clientY];
      this.gondola = document.getElementById('slotGondola');
      this.pid = false;
      document.documentElement.onmousemove = function(e) {
        e = e || event;
        if (!app.dragging) {
          app.hideDialogs();
          this.className += " moving";
          for (var x = 0; x < self.slots; x++) {
            self.rows[x] = document.getElementById('f' + (self.frame % 6) + 's' + (self.slot + x)).parentNode;
            self.rows[x].className += " blacked";
          }
          self.gondola.rows[0].className = (self.select) ? "selected" : "";
          self.gondola.rows[0].cells[1].firstChild.firstChild.nodeValue = self.rows[0].cells[1].firstChild.firstChild.nodeValue;
          self.gondola.rows[0].cells[0].style.height = (18 * self.slots) + "px";
          self.gondola.style.visibility = "visible";
        } app.dragging = true;
        self.gondola.style.left = self.posn[0] + e.clientX + "px";
        self.gondola.style.top = self.posn[1] + e.clientY + "px";
        var match, scrld = scrollDist(), below = dragon.getZoneFromPoint(e.clientX + scrld[0], e.clientY + scrld[1]);
        if (below != self.pid) {
          if (typeof self.pid == "string") {
            var prev = document.getElementById(self.pid).parentNode;
            if (prev.className != "closed");
              prev.className = prev.className.removeWord("hover");
          }
          if (typeof below == "string" && (match = app.trpattern.exec(below))) {
            var ftar = parseInt(match[1]) + app.page * app.fpp;
            if (!(frame[ftar].isFull() & 4) || modules[modHash[frame[self.frame].slot[self.slot].name]].allowed(frame[ftar].type, frame[ftar].frame, parseInt(match[2]))) {
              var next = document.getElementById(below).parentNode;
              if (next.className != "closed") next.className += " hover";
            }
          } self.pid = below;
        } e.cancelBubble = true;
        return false;
      };
      document.documentElement.onmouseup = function(e) {
        if (app.dragging) {
          e = e || event;
          app.dragging = false;
          self.gondola.style.visibility = "";
          self.className = self.className.removeWord("moving");
          for (var x = 0; x < self.rows.length; x++)
            self.rows[x].className = self.rows[x].className.removeWord("blacked");

          var match, prev = document.getElementById(self.pid).parentNode;
          if (typeof self.pid == "string" && prev.className != "closed") {
            prev.className = prev.className.removeWord("hover");
            if (match = app.trpattern.exec(self.pid)) {
              var ftar = parseInt(match[1]) + app.page * app.fpp, star = parseInt(match[2]);
              if (!isNaN(ftar) && !isNaN(star)) {
                var holdcur = app.cursor, putback = false, blocked = false;
                var modu = cloneObject(frame[self.frame].slot[self.slot]);
                if (e.shiftKey) frame[self.frame].place(self.slot, 0, false);
                if (self.slots > 1) {
                  for (var x = star; x >= star && x >= 0; x--)
                    if (blocked === false && (frame[ftar].slot[x].multi || frame[ftar].slot[x].name != "Empty Slot")) blocked = x;
                  if (blocked !== false) {
                    for (var x = blocked + 1, canslide = true; x < blocked + self.slots && x < frame[ftar].slots; x++)
                      if (frame[ftar].slot[x].name != "Empty Slot") canslide = false;
                    if (canslide) { star = blocked + 1; blocked = false; }
                  } else star = Math.max(0, star);
                }
                if (blocked === false) {
                  if (!modules[modHash[modu.name]].frames.find(frame[ftar].frame) && !(frame[ftar].isFull() & 4)) {
                    frame[ftar].switchType(modules[modHash[modu.name]].frames[0]);
                    star = Math.min(star, frame[ftar].slots - modules[modHash[modu.name]].slots);
                  }
                  if (frame[ftar].place(star, modHash[modu.name], true)) {
                    frame[ftar].slot[star].subTransfer(modu);
                    frame[ftar].refresh();
                  } else if (e.shiftKey) putback = true;
                } else if (e.shiftKey) putback = true;
                if (putback) {
                  frame[self.frame].place(self.slot, modHash[modu.name], true);
                  frame[self.frame].slot[self.slot].subTransfer(modu);
                  frame[self.frame].refresh();
                }
                if (!self.select) {
                  app.cursor = holdcur;
                  if (ftar == app.working || self.frame == app.working)
                    frame[app.working].highlight(app.cursor, true);
                }
              }
            }
          }
        }
        document.documentElement.onmousemove = null;
        document.documentElement.onmouseup = null;
      };
    }
  } return false;
}


/* ***************************************************************************************************************************
 * Object which handles dropping of the Selected and Recent Panels
 *
 */
function Dropzone() {
  var self = this;

  this.main = [[0, 0], [0, 0]];
  this.fram = [];
  this.body = [];
  this.zone = [];
  this.mPos = findPos(document.getElementById('mainlayout'));
  this.pPos = findPos(document.getElementById('mainlayout'));

  this.updateAdjustment = function() {
    self.pPos = findPos(document.getElementById('mainlayout'));
  };

  if (window.addEventListener) {
    window.addEventListener("resize", this.updateAdjustment, false);
  } else if (window.attachEvent) window.attachEvent("onresize", this.updateAdjustment);

  document.documentElement.scrollTop = document.documentElement.scrollLeft = 0;
  var fbods = document.getElementById('frames').getElementsByTagName('tbody');
  for (var i = 0, fram; i < app.fpp; i++) {
    fram = fbods[i].parentNode;
    this.fram[i] = [[fram.offsetLeft, fram.offsetTop]];
    this.fram[i][1] = [this.fram[i][0][0] + fram.offsetWidth, this.fram[i][0][1] + fram.offsetHeight];
    this.fram[i].id = fram.id;

    this.body[i] = [findPos(fbods[i])];
    this.body[i][1] = [this.body[i][0][0] + fbods[i].offsetWidth, this.body[i][0][1] + fbods[1].offsetHeight];
    if (i == 0) this.main[0] = this.body[i][0];
    if (i == 5) this.main[1] = this.body[i][1];

    this.zone[i] = [];
    for (var j = 0, frows = fbods[i].getElementsByTagName('tr'); j < frows.length; j++) {
      this.zone[i][j] = {
        id: frows[j].cells[1].id,
        coord: [
          [this.body[i][0][0]                       , this.body[i][0][1] - frows[0].offsetTop + frows[j].offsetTop                         - 1],
          [this.body[i][0][0] + frows[j].offsetWidth, this.body[i][0][1] - frows[0].offsetTop + frows[j].offsetTop + frows[j].offsetHeight + 1]
        ]
      };
    }
  }

  this.getZoneFromPoint = function(x, y) {
    x -= this.pPos[0] - self.mPos[0];
    y -= this.pPos[1] - self.mPos[1];

    // If x or y is outside of the main area
    if (x < this.main[0][0] || x > this.main[1][0] || y < this.main[0][1] || y > this.main[1][1]) return -3;

    // Find which of the body sections our coords are in
    for (var i = 0, f = -1; i < this.body.length; i++)
      if (x > this.body[i][0][0] && x < this.body[i][1][0] && y > this.body[i][0][1] && y < this.body[i][1][1]) f = i;

    // Find out which of the rows is being hovered
    if (f > -1) {
      for (var j = 0; j < this.zone[f].length; j++)
        if (x > this.zone[f][j].coord[0][0] && x < this.zone[f][j].coord[1][0] && y > this.zone[f][j].coord[0][1] && y < this.zone[f][j].coord[1][1]) return this.zone[f][j].id;
    } else return -2;
    return -1;
  };

  this.getFrameFromPoint = function(x, y) {
    // If x or y is outside of the main area
    if (x < 0 || x > this.main[1][0] - this.main[0][0] || y < 0 || y > this.main[1][1] - this.main[0][1]) return -3;

    // Find which of the frames our coords are in
    for (var i = 0, f = -1; i < this.fram.length; i++)
      if (x > this.fram[i][0][0] && x < this.fram[i][1][0] && y > this.fram[i][0][1] && y < this.fram[i][1][1]) return this.fram[i].id;

    return -1;
  };
}





/* ***** Global Variables ****************************************** */
var page = enclosures[encHash[window.location.pathname.replace(/^.+[\/\\]([^\/\\]+?)(\.(html|php))?$/, "$1")]];
var app = new Application(), dragon, Pop;
for (var x = 0, frame = []; x < app.fpp; x++) frame.push(new Frame(x, page.frame))


/* ***************************************************************************************************************************
 * Prepare the document onload
 *
 */
function createOnload() {

  // ***** Hook dragon claws into the headers of popup "windows" ******
  Pop = {
    selector: new Popup('selector', true, false),
    options: new Popup('options', true, false),
    savedNotice: new Popup('savednotice', true, true),
    waiter: new Popup('waiter', false, true),
    summary: new Popup('summary', true, true),
    fileComplete: new Popup('filecomplete', true, true),
    IPElem: new Popup('ip', true, false),
    framePreview: new Popup('framepre', true, true),
    samplePalettes: new Popup('samplepalettes', true, true)
  };
  Pop.waiter.show();

  Pop.selector.position = Pop.options.position = function() {
    var fwrk = document.getElementById('f' + (app.working % app.fpp));
    var ppos = findPos(fwrk), mpos = findPos(document.getElementById('mainlayout'));
    this.element.style.top = (ppos[1] - mpos[1] + 5) + "px";
    this.element.style.left = (ppos[0] - mpos[0] + fwrk.offsetWidth - 10) + "px";
  };


  // ***** Apply misc event listeners *********************************
  var cancelSelection = ['frames', 'frames_add', 'recent'];
  for (var x = 0, cs; cs = cancelSelection[x++];) selectDisable(document.getElementById(cs));

  document.getElementsByName('info_project')[0].onblur = function() {
    document.getElementsByTagName('h2')[0].firstChild.nodeValue = this.value;
    return true;
  };
  Pop.selector.element.getElementsByTagName('ul')[0].onscroll = function(e) {
    (e || event).cancelBubble = true;
    document.getElementsByName('module_filter')[0].focus();
    return true;
  };
  for (var x = 0, lis = Pop.samplePalettes.element.getElementsByTagName('li'); x < lis.length; x++) {
    lis[x].onmouseover = function() { this.className = "hover"; };
    lis[x].onmouseout = function() { this.className = ""; };
  }
  Pop.IPElem.element.getElementsByTagName('button')[0].onclick = function() { app.ipDisplay(); };
  Pop.fileComplete.element.getElementsByTagName('a')[0].onclick = function() {
    var remTaint = Taint;
    Taint = false;
    setTimeout(function() {
      Taint = remTaint;
      Pop.fileComplete.hide();
    }, 5);
  };

  var slider = document.getElementById('frames_pages');
  slider.interval = false;
  slider.slide = function(direction) {
    if (!this.style.left) this.style.left = "0px";
    var maxi = this.parentNode.offsetWidth - this.offsetWidth;
    switch (direction) {
      case "left":
        this.style.left = Math.min(0, parseInt(this.style.left) + 10) + "px";
        if (this.style.left == "0px") {
          clearInterval(this.interval);
          this.slideall = false;
        } break;
      case "right":
        this.style.left = Math.max(maxi, parseInt(this.style.left) - 10) + "px";
        if (this.style.left == maxi + "px") {
          clearInterval(this.interval);
          this.slideall = false;
        } break;
      case "pageleft":
        this.style.left = Math.min(0, parseInt(this.style.left) + this.parentNode.offsetWidth) + "px";
        break;
      case "pageright":
        this.style.left = Math.max(maxi, parseInt(this.style.left) - this.parentNode.offsetWidth) + "px";
        break;
      case "endleft": this.style.left = "0px"; break;
      case "endright": this.style.left = maxi + "px";
    }
  };
  var sliderLeft = document.getElementById('frames_page_left');
  var sliderRight = document.getElementById('frames_page_right');
  sliderLeft.onmousedown = function() {
    clearInterval(slider.interval);
    slider.interval = setInterval(function() { slider.slide('left'); }, 30);
  };
  sliderRight.onmousedown = function() {
    clearInterval(slider.interval);
    slider.interval = setInterval(function() { slider.slide('right'); }, 30);
  };
  sliderLeft.onclick = sliderRight.onclick = function() { return false; }
  sliderLeft.ondblclick = function() {
    clearInterval(slider.interval);
    slider.slide('endleft');
  };
  sliderRight.ondblclick = function() {
    clearInterval(slider.interval);
    slider.slide('endright');
  };
  sliderLeft.onmouseup = sliderLeft.onmouseout = sliderRight.onmouseup = sliderRight.onmouseout = function() {
    if (!slider.slideall) clearInterval(slider.interval);
  };

  document.getElementById('instructions').onclick = function() {
    if (typeof app.instWindow != "object" || app.instWindow.closed) {
      app.instWindow = window.open("Bulk.help.html", "manual", "width=550,height=550,scrollbars");
    } else app.instWindow.location.href = "Bulk.help.html";
    app.instWindow.focus();
  };
  var tpal = document.getElementById('tabs_palette');
  if (tpal) tpal.onclick = function() {
    if (app.recent.length) {
      for (var x = 0, data = ""; x < app.recent.length; x++)
        data += app.recent[x].fullName(2) + "\n";
      window.location.href = "saverecall?method=palsave&data=" + encodeURIComponent(data);
    } else alert('Recently selected panels palette is empty');
  };


  // ***** Apply frame table event listeners **************************
  var tabs = document.getElementById("frames").getElementsByTagName('table');
  for (var x = 0; x < 6; x++) {
    tabs[x].tHead.getElementsByTagName('input')[0].onkeyup = (function(x) { return function(e) {
      e = e || event;
      frame[x + app.page * app.fpp].frameid = this.value;
      if (e.keyCode == 40 || e.keyCode == 13) {
        frame[x + app.page * app.fpp].highlight(0, true);
        this.blur();
        return false;
      } return true;
    }})(x);


    // ***** Frame dragging
    tabs[x].tHead.onmousedown = function(e) {
      if (this.parentNode.className.indexOf('empty') > -1) return false;

      e = e || event;
      var self = this, scrDist = scrollDist();

      this.parentNode.className += " moving";
      this.client = [e.clientX + scrDist[0], e.clientY + scrDist[1]];
      this.pos = [this.parentNode.offsetLeft, this.parentNode.offsetTop];
      this.over = 0;

      document.documentElement.onmousemove = function(e) {
        if (app.view != "edit") app.switchControl('edit');

        e = e || event;
        var scrDist = scrollDist();
        var coord = [e.clientX - self.client[0] + scrDist[0], e.clientY - self.client[1] + scrDist[1]];
        var midPt = [coord[0] + self.parentNode.offsetWidth / 2, coord[1] + self.parentNode.offsetHeight / 2];

        self.parentNode.style.left = coord[0] + "px";
        self.parentNode.style.top = coord[1] + "px";

        // use the midPt to find if we have dragged this frame over another.
        // document.getElementsByName('info_author')[0].value = "(" + (midPt[0] + self.pos[0]) + ", " + (midPt[1] + self.pos[1]) + ")";
        // document.getElementsByName('info_project')[0].value = dragon.getFrameFromPoint(midPt[0] + self.pos[0], midPt[1] + self.pos[1]);

        var over = dragon.getFrameFromPoint(midPt[0] + self.pos[0], midPt[1] + self.pos[1]);
        if (self.over != over) {
          for (var x = 0, obj; x < app.fpp; x++) {
            obj = document.getElementById("f" + x);
            if (!obj.step) obj.step = 0;
            if (!obj.vector) obj.vector = [0, 0];
            if (!obj.animOut) obj.animOut = function() {
              if (--this.step == 0) {
                clearInterval(this.interval);
                this.style.position = "";
              }
              this.style.left = Math.ceil(this.vector[0] * this.step) + "px";
              this.style.top = Math.ceil(this.vector[1] * this.step) + "px";
            };
            if (!obj.animIn) obj.animIn = function() {
              this.style.left = Math.ceil(this.vector[0] * this.step) + "px";
              this.style.top = Math.ceil(this.vector[1] * this.step) + "px";
              if (++this.step > 3) clearInterval(this.interval);
            };
            if ("f" + x != over && obj != self.parentNode) {
              clearInterval(obj.interval);
              if (obj.step) obj.interval = setInterval((function(obj) { return function() { obj.animOut(); }})(obj), 10);
            }
          }
          self.over = over;
          var obj = false;
          if (typeof self.over == "string") {
            if ((obj = document.getElementById(self.over)) && obj !== self.parentNode) {
              obj.style.left = obj.style.top = "0px";
              obj.style.position = "relative";
              obj.vector = [Math.floor((self.pos[0] - obj.offsetLeft) / 100), Math.floor((self.pos[1] - obj.offsetTop) / 100)];
              clearInterval(obj.interval);
              obj.interval = setInterval(function() { obj.animIn(); }, 10);
            }
          }
        }
      };
      document.documentElement.onmouseup = function(e) {
        for (var x = 0, obj; x < app.fpp; x++) {
          obj = document.getElementById("f" + x);
          if (!obj.step) obj.step = 0;
          if (!obj.vector) obj.vector = [0, 0];
          clearInterval(obj.interval);
          if (obj.step) obj.interval = setInterval((function(obj) { return function() { obj.animOut(); }})(obj), 10);
        }

        self.parentNode.className = self.parentNode.className.removeWord("moving");
        self.parentNode.style.left = self.parentNode.style.top = "0px";
        document.documentElement.onmousemove = null;
        document.documentElement.onmouseup = null;

        if (self.over != self.parentNode.id && (obj = document.getElementById(self.over))) {
          // swap the contents of the dragged frame with the contents of obj
        }
      };
    };



    tabs[x].tHead.onclick = (function(x) { return function() {
      if (app.working != x + app.page * app.fpp) frame[x + app.page * app.fpp].highlight(0, true);
    }})(x);
    for (var y = 0, tabslots = tabs[x].tBodies[0].getElementsByTagName("tr"); y < tabslots.length; y++) {
      tabslots[y].cells[0].onclick = tabslots[y].cells[1].onclick = (function(x, y) { return function(e) {
        if (this.parentNode.className == "closed") return false;
        e = e || event;
        var reduc = app.working % app.fpp, wframe = x + app.page * app.fpp;
        if (frame[wframe].slot[y]) {
          if (wframe == app.working) {
            if (e.shiftKey) {
              for (var i = app.cursor; i != y;) {
                var row = document.getElementById("f" + reduc + "s" + i).parentNode;
                if (row.className.indexOf('multiselect') === -1) row.className += " multiselect";
                if (i < y) { i++; app.multimove++; } else { i--; app.multimove--; }
              } frame[wframe].highlight(y, false);
              return false;
            } else if (e.ctrlKey || e.metaKey) {
              for (var i = 0, sel = y, k = false, row; i < frame[wframe].slots; i++) {
                row = document.getElementById("f" + reduc + "s" + i).parentNode;
                if (i != y) {
                  row.className = row.className.replace(/ ?selected ?/g, "multiselect");
                  if (sel == y && row.className.indexOf('select') > -1) sel = i;
                } else {
                  if (row.className.indexOf('select') > -1) {
                    for (var j = i, k = true, row2; j < i + frame[wframe].slot[y].slots; j++) {
                      row2 = document.getElementById("f" + reduc + "s" + j).parentNode;
                      row2.className = row2.className.removeWord("multiselect").removeWord("selected");
                    }
                  } i += frame[wframe].slot[y].slots - 1;
                }
              } app.multimove = 0;
              frame[wframe].highlight((k) ? sel : y, false);
              return false;
            } frame[wframe].highlight(y, true);
          } else frame[wframe].highlight(y, true);
        }
      }})(x, y);
      tabslots[y].cells[0].oncontextmenu = tabslots[y].cells[1].oncontextmenu = (function(x, y) { return function(e) {
        if (this.parentNode.className == "closed") return false;
        (e = e || event).returnValue = false;
        return contextMenu(e, x, y);
      }})(x, y);
      tabslots[y].cells[0].ondblclick = tabslots[y].cells[1].ondblclick = (function(x, y) { return function() {
        if (this.parentNode.className == "closed") return false;
        app.exitLabelMode();
        app.selectorDisplay(true);
      }})(x, y);
      tabslots[y].onmousedown = slotDragon;
    }
    tabs[x].tFoot.getElementsByTagName('img')[0].onclick = (function(x) { return function() {
      frame[x + app.page * app.fpp].setrps(!frame[x + app.page * app.fpp].hasrps);
    }})(x);
    tabs[x].tFoot.getElementsByTagName('td')[0].onclick = (function(x) { return function() {
      frame[x + app.page * app.fpp].toggleLabels();
    }})(x);
    var tabtype = tabs[x].tFoot.getElementsByTagName('div')[0];
    tabtype.onclick = function() { this.getElementsByTagName('ul')[0].style.display = "block"; }
    var tabtypeul = tabtype.getElementsByTagName('ul')[0];
    tabtypeul.onmouseout = function() { var self = this; this.timeout = setTimeout(function() { self.style.display = ""; }, 5); };
    tabtypeul.onmouseover = function() { clearTimeout(this.timeout); };
    while (tabtypeul.firstChild) tabtypeul.removeChild(tabtypeul.firstChild);
    for (var y = 0; y < enclosures.length; y++) {
      if ((enclosures[y].online || !app.online) && enclosures[y].slots && enclosures[y].slots <= 16) {
        var li = document.createElement('li');
            li.title = enclosures[y].name;
            li.appendChild(document.createTextNode(enclosures[y].frame));
            li.onmouseover = function() { this.className = "hover"; };
            li.onmouseout = function() { this.className = ""; };
            li.onclick = (function(x) { return function(e) {
              (e || event).cancelBubble = true;
              this.parentNode.style.display = "";
              frame[x + app.page * app.fpp].switchType(this.firstChild.nodeValue.trim());
            }})(x);
          tabtypeul.appendChild(li);
      }
    }
    tabs[x].tFoot.getElementsByTagName('strong')[0].onclick = (function(x) { return function(e) {
      (e || event).cancelBubble = true;
      Pop.IPElem.element.style.left = ((x % 3) * 235) + 37 + "px";
      Pop.IPElem.element.style.top = Math.floor(x / 3) * 335 + 100 + "px";
      frame[x + app.page * app.fpp].highlight(app.cursor, false);
      app.ipDisplay();
    }})(x);
  }


  // ***** Create the file input elements *****************************
  app.refreshFileInput(document.getElementById('upload_config'), "text/csv, text/comma-separated-values");
  app.refreshFileInput(document.getElementById('upload_palette'), "text/plain");


  // ***** Apply listeners to the IP dialogue *************************
  for (var x = 0, spans = Pop.IPElem.element.getElementsByTagName('div')[0].getElementsByTagName('span'); x < spans.length; x++) {
    for (var y = 0, inps = spans[x].getElementsByTagName('input'); y < inps.length; y++) {
      inps[y].onfocus = function() {
        if (this.value === "0") this.value = "";
        this.focused = this.value;
        return true;
      }
      inps[y].onblur = function() {
        if (this.value === "") this.value = "0";
        return true;
      }
      inps[y].onkeydown = (function(y) { return function(e) {
        (e = e || event).cancelBubble = true;
        switch (e.keyCode) {
          case 13:
            if (Pop.IPElem.element.style.display == "block") {
              app.ipDisplay();
              this.blur();
            } break;
          case 46: if (!window.opera) return true;
          case 110: case 190:
            if (y < 3 && this.value != "") {
              var self = this;
              setTimeout(function() { self.nextSibling.nextSibling.focus(); }, 0);
            } break;
          case 27:
            app.hideDialogs();
            this.blur();
            break;
          default: return true;
        } return false;
      }})(y);
      inps[y].onkeypress = function(e) {
        (e = e || event).cancelBubble = true;
        switch (e.keyCode) {
          case 13: case 27: case 46: return false;
        } return true;
      };
      inps[y].onkeyup = (function(x, y) { return function(e) {
        (e || event).cancelBubble = true;
        this.value = this.value.replace(/[^\d]/g, "");
        if (parseInt(this.value) > 255) this.value = 255;
        if (this.value.length == 3 && y < 3 && this.focused != this.value) this.nextSibling.nextSibling.focus();
        if (x * 4 + y < 8) {
          var octets = Pop.IPElem.element.getElementsByTagName('input');
          var ip = [octets[0].value, octets[1].value, octets[2].value, octets[3].value].join(".");
          var sm = [octets[4].value, octets[5].value, octets[6].value, octets[7].value].join(".");
          document.getElementById('ip_broadcast').getElementsByTagName('span')[0].firstChild.nodeValue = getBroadcastIP(ip, sm);
        } return false;
      }})(x, y);
    }
  }


  // ***** Create the iframe recall target ****************************
  var iframe = document.createElement('iframe');
      iframe.src = "";
      iframe.name = "lizard";
  document.body.appendChild(iframe);
  try {
    if (window.frames[0].window.name != 'lizard')
      window.frames[0].window.name = 'lizard';
  } catch(e) {}
  for (var x = 0, f; f = document.forms[x++];)
    if (f.action.indexOf('saverecall') > -1) f.target = "lizard";


  setTimeout(function() { dragon = new Dropzone(); }, 5);


  // ***** Fix for IE hiding the info column
  document.getElementById('column').style.display = "block";
}
