
// ------------------------------ Common functions ---------------------------

var bShowAlert = true;
function gemsError(xRet,sMsg) {
  if (bShowAlert) window.alert("GEMS:"+sMsg);
  return xRet;
}


// ----------------------- Generic Validation Functions ----------------------

/* This finds the first password value and, if changed, makes sure that
 * the confirm value is the same.
 */
function gems2XConfirmPassword(oCtl,oForm) {
  for (var i=0; i < oForm.elements.length; i++) {
    var oObj = oForm.elements[i];
    if (oObj.oGemCtl == null) continue;
    var oPass = oObj.oGemCtl;
    if (oPass.sType == "password") {
      var sDelta = oPass.getDelta();
      if (sDelta != null) {
        if (oCtl.getCtlValue() != oPass.getCtlValue()) {
          return "Differs from password field";
        } else {
          return null;
        }
      } else {
        return null;
      }
    }
  }
  return "lost password field";
}

// --------------------------- Generic Form Functions ------------------------
function gems2XSubmitClick(oBut) {
  var oForm=oBut.form;
  if (oForm == null) return gemsError(null,"No Form");
  var sName = oBut.name;
  if (sName == null) return gemsError(null,"No Button name");
  return gemsSubmitForm(oForm,sName);
}

function gems2XSubmitForm(oForm,sName) {
  var oTub = new TUB();
  for (var i=0; i < oForm.elements.length; i++) {
    var oObj = oForm.elements[i];
    gemsAddValue(oTub,oObj,sName);
  }
  var s = oTub.toText();
  return s;
}

function gems2XCheckRequired(oForm) {
  var bOK = true;
  for (var i=0; i < oForm.elements.length; i++) {
    var oObj = oForm.elements[i];
    if (oObj.oGemCtl == null) continue;
    var oCtl = oObj.oGemCtl;
    if (oCtl.bRequired) {
      if (oCtl.isEmpty()) {
        gemsIssueValMsg(oCtl,"Requires a value");
        bOK = false;
      } else {
        gemsIssueValMsg(oCtl,"");
      }
    }
  }
  return bOK;
}

function gems2XClientValidate(oForm) {
  var bOK = true;
  for (var i=0; i < oForm.elements.length; i++) {
    var oObj = oForm.elements[i];
    if (oObj.oGemCtl == null) continue;
    var oCtl = oObj.oGemCtl;
    if (oCtl.oBV != null) {
      var sMsg = oCtl.oBV(oCtl,oForm);
      if (sMsg != null) {
        gemsIssueValMsg(oCtl,sMsg);
        bOK = false;
      } else {
        gemsIssueValMsg(oCtl,"");
      }
    }
  }
  return bOK;
}

function gems2XIssueValMsg(oCtl,sMsg) {
  var oMsgFld = getElement("vf"+oCtl.sName.substring(2));
  if (oMsgFld == null) {lwsLog("lost "+oCtl.sName); return;}
  oMsgFld.innerHTML = sMsg;
}

function gems2XGetDelta(oForm) {
  var oTub = null;
  for (var i=0; i < oForm.elements.length; i++) {
    var oObj = oForm.elements[i];
    if (oObj.oGemCtl == null) continue;
    var oCtl = oObj.oGemCtl;
    var sDelta = oCtl.getDelta();
    //lwsLog("Looking at "+oObj.name+" "+oCtl.sName+" delta=/"+sDelta+"/");
    if (sDelta != null) {
      if (oTub == null) oTub = new TUB();
      var sKey = oCtl.sName.substring(2);
      if (oTub.getValue(sKey) == null) {
        var oFld = new TUB();
        oFld.add("V",sDelta);
        oFld.add("T",oCtl.sType);
        oTub.addObj(sKey,oFld);
      }
    }
  }
  if (oTub == null) return null;
  var s = oTub.toText();
  //lwsLog("getDelta:"+s);
  return s;
}

function gemsOnBlur() {
  var oInp = this;
  var oForm = oInp.form;
  gemsOnCtlSwitch(oForm,oInp,"blur");
}

function gemsOnFocus() {
  var oInp = this;
  var oForm = oInp.form;
  gemsOnCtlSwitch(oForm,oInp,"focus");
}

function gemsOnCtlSwitch(oForm,oInp,sEvent) {
  if (oInp == null) {
    gemsLog("Switch CTL on "+sEvent+" oInp=null");
    return;
  }
  if (oForm == null) {
    gemsLog("Switch CTL on "+sEvent+" oForm=null oInp="+oInp.name);
    return;
  }
  gemsLog("Switch CTL on "+sEvent+": form="+oForm.name+" name="+oInp.name);
  if (oForm.onGemsBlur != null) {
    oForm.onGemsBlur(oForm,oInp);
  }
}

function gems2XAddValue(oTub,oObj,sButName) {
  var sName = oObj.name;
  var sVal  = null;
  var sType = oObj.type;
  if (oObj.getValue != null) {
    sVal   = oObj.getValue(oObj);
    sName  = oObj.getName(oObj);
  } else {
    switch(sType) {
      case "text":
      case "hidden":
      case "password":
      case "textarea":
        sVal  = oObj.value;
        break;
      case "radio":
      case "checkbox":
        if (oObj.checked) sVal = oObj.value;
        break;
      case "select-one":
      case "select-multiple":
        sVal = gemsGetSelected(oObj);
        break;
      case "button":
      case "submit":
        if (sName == sButName) sVal = oObj.value;
        break;
      default:
        sVal = "UnknownType:"+sType;
        break;
    }
  }
  addTubValue(oTub,sName,sVal);
}

function addTubValue(oTub,sKey,sVal) {
  if (sVal == null) return;
  if (sVal == "") return;
  if (oTub.getValue(sKey) == null) { //Ignore duplicated field names in form
    oTub.add(sKey,sVal);
  }
}

function gems2XGetSelected(oSel) {
  var sVal = null;
  for(var i=0; i < oSel.options.length; i++) {
    var oOpt = oSel.options[i];
    if (oOpt.selected) {
      if (sVal == null) {
        sVal = oOpt.value;
      } else {
        sVal += ";" + oOpt.value;
      }
    }
  }
  return sVal;
}


// -----------------------------------------------------------------
// --------------------    Gems Control Factory    -----------------
// -----------------------------------------------------------------

function makeGemsCtl(oForm,sType,sName,sValue,sTitle,sParms,bInternal) {
  if (bInternal == null) bInternal = false;
  var oCtl;
  switch(sType) {
    case "Checkbox":   oCtl = new GemsCtlCheckbox();   break;
    case "Text":       oCtl = new GemsCtlText();       break;
    case "Radio":      oCtl = new GemsCtlRadio();      break;
    case "Password":   oCtl = new GemsCtlPassword();   break;
    case "Textarea":   oCtl = new GemsCtlTextarea();   break;
    case "Select":     oCtl = new GemsCtlSelect();     break;
    case "Join":       oCtl = new GemsCtlJoin();       break;
    case "Link":       oCtl = new GemsCtlLink();       break;
    case "Lab":        oCtl = new GemsCtlLab();        break;
    default:
      return gemsError(null,"gemsCtl type "+sType+" not supported");
  }
  oCtl.setValues(oForm,sName,sValue,sTitle,sParms);
  if (!bInternal) {
    dw(oCtl.getText());
    oCtl.linkObjs();
  }
  return oCtl;
}


// -----------------------------------------------------------------
// --------------------  Gems Supported Controls  -----------------
// -----------------------------------------------------------------

new GemsCtl();

// Base function
function GemsCtl() {
  GemsCtl.prototype.getName     = getName;
  GemsCtl.prototype.getTitle    = getTitle;
  GemsCtl.prototype.getValue    = getValue;
  GemsCtl.prototype.getText     = getText;
  GemsCtl.prototype.getParms    = getParms;
  GemsCtl.prototype.setValues   = setValues;
  GemsCtl.prototype.linkObjs    = linkObjs;
  GemsCtl.prototype.getCtlValue = getCtlValue;
  GemsCtl.prototype.getDelta    = getDelta;
  GemsCtl.prototype.isEmpty     = isEmpty;
  GemsCtl.prototype.prebuilt    = prebuilt;
  return;
  function getName()  {return this.sName;}
  function getValue() {return this.sValue;}
  function getTitle() {return this.sTitle;}
  function getParms() {return this.sParms;}
  function setValues(oForm,sName,sValue,sTitle,sParms) {
    this.oForm  = oForm;
    this.sName  = sName;
    this.sValue = sValue;
    this.sTitle = sTitle;
    this.sParms = sParms;
  }
  function getText() {
    var s = "<input type="+this.sType+" name="+this.sName;
    if (this.getTitle() != null) s+=" title='"+this.getTitle()+"'";
    if (this.getValue() != null) s+=" value='"+this.getValue()+"'";
    if (this.getParms() != null) s+=" "+this.getParms();
    s += ">";
    return s;
  }
  function linkObjs() {
    if (this.oForm == null) {
      this.oObj = getElement(this.sName);
    } else {
      this.oObj = this.oForm[this.sName];
    }
    if (this.oObj != null) {
      this.oObj.oGemCtl = this;
      this.oObj.onblur = gemsOnBlur;
      this.oObj.onfocus = gemsOnFocus;
    }
  }
  function getCtlValue() {
    if (this.oObj == null) return null;
    return this.oObj.value;
  }
  function isEmpty() {
    var sVal = this.getCtlValue();
    if (sVal == null) return true;
    if (sVal == "") return true;
    return false;
  }
  function getDelta() {
    if (this.oObj == null) return null;
    var sCur = this.oObj.value;
    var sPrev = this.oObj.defaultValue;
    if (sPrev == sCur) return null;
    return sCur;
  }
  function prebuilt() {}
}

// GemCtlLab
function GemsCtlLab() {
  this.sType = "lab";
  GemsCtlLab.prototype.getText = getText;
  function getText(sCol,nRow) {
    var s2  = "<span id="+this.getName+">"+this.sValue+"</span>";
    return s2;
  }
}
GemsCtlLab.prototype = new GemsCtl();

// GemCtlCheckbox
function GemsCtlCheckbox() {
  this.sType = "checkbox";
  return;
}
GemsCtlCheckbox.prototype = new GemsCtl();

// GemCtlText
function GemsCtlText() {
  this.sType = "text";
  return;
}
GemsCtlText.prototype = new GemsCtl();

// GemCtlRadio
function GemsCtlRadio() {
  this.sType = "radio";
  return;
}
GemsCtlRadio.prototype = new GemsCtl();

// GemCtlPassword
function GemsCtlPassword() {
  this.sType = "password";
  return;
}
GemsCtlPassword.prototype = new GemsCtl();

// GemCtlTextarea
function GemsCtlTextarea() {
  this.sType = "textarea";
  GemsCtlTextarea.prototype.getText = getText;
  return;
  function getText() {
    var s = "<textarea name="+this.sName;
    if (this.getTitle() != null) s+=" title='"+this.getTitle()+"'";
    if (this.getParms() != null) s+=" "+this.getParms();
    s += ">";
    if (this.getValue() != null) s+=this.getValue();
    s += "</textarea>";
    return s;
  }
}
GemsCtlTextarea.prototype = new GemsCtl();

// GemCtlLink
function GemsCtlLink() {
  this.sType = "link";
  GemsCtlLink.prototype.getText = getText;
  return;
  function getText() {
    var s = "<a name="+this.sName;
    var sHRef = "";
    var sDisp = "";
    var oVal  = this.getValue();
    if (isArray(oVal)) {
      sHRef = oVal[0];
      sDisp = oVal[1];
    } else {
      if (oVal != null) {
        sHRef = oVal;
        sDisp = oVal;
      }
    }
    if (this.getTitle() != null) s+=" title='"+this.getTitle()+"'";
    if (this.getParms() != null) s+=" "+this.getParms();
     s+=" href=\""+sHRef+"\"";
    s += ">";
    s += sDisp;
    s += "</a>";
    return s;
  }
}
GemsCtlLink.prototype = new GemsCtl();

// GemCtlSelect
function GemsCtlSelect() {
  this.sType = "select";
  GemsCtlSelect.prototype.getText = getText;
  return;
  function getText() {
    var s = "<select id="+this.sName+" name="+this.sName;
    if (this.getTitle() != null) s+=" title='"+this.getTitle()+"'";
    if (this.getParms() != null) s+=" "+this.getParms();
    s += ">";
    var sVal = this.getValue();
    if (isArray(sVal)) {
      s += getArrOptions(sVal);
    } else {
      if ((sVal != null) && (sVal.length > 2)) {
        s+=getOptions(sVal);
      } else {
        s+="<option></option>"
      }
    }
    s += "</select>";
    return s;
  }
  function getOptions(sVal) {
    var s = "";
    var sOptSep = sVal.substring(0,1);
    var sFldSep = sVal.substring(1,2);
    var aOpts = sVal.substring(2).split(sOptSep);
    var iMax = aOpts.length;
    for(var i=0; i < iMax; i++) {
      var sOpt = aOpts[i];
      if (sOpt.length > 0) {
        s += getOption(sFldSep,sOpt);
      }
    }
    return s;
  }

  function getOption(sFldSep,sOpt) {
    var aFlds = sOpt.split(sFldSep);
    return getArrOption(aFlds[0],aFlds[1],aFlds[2],aFlds[3]);
  }

  function getArrOptions(aOpts) {
    s = "";
    var iMax = aOpts.length;
    for(var i=0; i < iMax; i++) {
      var aOpt = aOpts[i];
      s += getArrOption(aOpt[0],aOpt[1],aOpt[2]);
    }
    return s;
  }

  function getArrOption(sVal,sDisp,sTitle,sSel) {
    if (sDisp == null) sDisp = sVal;
    if (sDisp.length == 0) sDisp = sVal;
    if (sTitle == null) sTitle = '';
    if (sTitle == '') sTitle = sDisp;
    sTitle = " title='"+sTitle+"'";
    if (sSel == null) sSel = '';
    if (sSel.length > 0) sSel = "selected";
    return "<option "+sSel+" value='"+sVal+"'"+sTitle+">"+sDisp+"</option>";
  }
}
GemsCtlSelect.prototype = new GemsCtl();

// GemCtlJoin
function GemsCtlJoin() {
  this.sType = "join";
  GemsCtlJoin.prototype.getText     = getText;
  GemsCtlJoin.prototype.linkObjs    = linkObjs;
  GemsCtlJoin.prototype.getCtlValue = getCtlValue;
  GemsCtlJoin.prototype.getDelta    = getDelta;
  GemsCtlJoin.prototype.prebuilt    = prebuilt;
  return;
  function getText() {
    var oParts    = getParts(this.sValue);
    var sLftVal   = oParts[0];
    var sRgtVal   = oParts[1];
    oParts        = getParts(this.sTitle);
    var sLftTitle = oParts[0];
    var sRgtTitle = oParts[1];
    oParts        = getParts(this.sParms);
    var sLftParms = oParts[0];
    var sRgtParms = oParts[1];
    this.sLftName = "_L_"+this.sName;
    this.sRgtName = "_R_"+this.sName;
    this.oLft     = makeGemsCtl(this.oForm,"Select",this.sLftName,sLftVal,sLftTitle," multiple class=joinList size=5 "+sLftParms,true);
    this.oRgt     = makeGemsCtl(this.oForm,"Select",this.sRgtName,sRgtVal,sRgtTitle," multiple class=joinList size=5 "+sRgtParms,true);
    var s =  "<table border=0>"
    s    += "<tr><td class=\"joinHead\">Selected</td><td></td><td class=\"joinHead\">Available</td></tr>"
    s    += "<tr><td>"+this.oLft.getText()+"</td>";
    s    += "<td>"+makeJoinButtons(this.sLftName,this.sRgtName)+"</td>";
    s    += "<td>"+this.oRgt.getText()+"</td>";
    s    += "</tr></table>"
    return s;
  }

  function prebuilt() {
    gemsLog("prebuilt "+this.sName);
    this.sLftName = "_L_"+this.sName;
    this.sRgtName = "_R_"+this.sName;
    this.linkObjs();
  }

  function linkObjs() {
    gemsLog("JoinLink attempt "+this.sLftName+" "+this.sRgtName);
    if (this.oForm == null) {
      this.oObj    = getElement(this.sLftName);
      this.oRgtObj = getElement(this.sRgtName);
    } else {
      this.oObj    = this.oForm[this.sLftName];
      this.oRgtObj = this.oForm[this.sRgtName];
    }
    if (this.oObj != null) {
      gemsLog("Linked left "+this.oObj.name);
      this.oObj.oGemCtl = this;
      this.oObj.onblur = gemsOnBlur;
    }
    if (this.oRgtObj != null) {
      gemsLog("Linked right "+this.oRgtObj.name);
      this.oRgtObj.oGemCtl = this;
      this.oRgtObj.onblur = gemsOnBlur;
    }
  }

  function getCtlValue() {
    if (this.oObj == null) return null;
    var oGet = this.oObj.getValue;
    if (oGet != null) return oGet(this.oObj);
    return this.oObj.value
  }

  function getDelta() {
    var sVals = this.getCtlValue();
    var sInit = this.sInitValue;
    //lwsLog("InitValue "+this.sName+":"+sInit);
    if (sInit == null) sInit = "";
    if (sVals == null) sVals = "";
    var aVals = sVals.split(";");
    var aInit = sInit.split(";");
    var aHash = new Object();
    for(var i=0; i < aVals.length; i++) {
      var sFld = aVals[i];
      if (sFld.length > 0) {
        aHash[""+sFld] = "T";
      }
    }
    for(var i=0; i < aInit.length; i++) {
      var sFld = aInit[i];
      if (sFld.length > 0) {
        if (aHash[""+sFld] == "T") {
          aHash[""+sFld] = null;
        } else {
          aHash[""+sFld] = "F";
        }
      }
    }
    var s = "";
    for(var sVbl in aHash) {
      if ((aHash[sVbl] == "T") || (aHash[sVbl] == "F")) {
        if (s != '') s+= ";";
        s += sVbl + "/" + aHash[sVbl];
      }
    }
    if (s == "") return null;
    return s;
  }

  function getParts(sPart) {
    var sLft = sPart;
    var sRgt = sPart;
    if (isArray(sPart)) {
      sLft = sPart[0];
      sRgt = sPart[0];
      if (sPart.length > 1) sRgt = sPart[1];
    }
    var oA = new Array();
    oA[0] = sLft;
    oA[1] = sRgt;
    return oA;
  }
  function makeJoinButtons(sLftName,sRgtName) {
    var oImgRgt = getElement("gemsImgRgtButIdle");
    var oImgLft = getElement("gemsImgLftButIdle");

    var sHan = " onMouseOver=\"gemsJoinButState(this,'Over');\" onMouseOut=\"gemsJoinButState(this,'Idle');\" onMouseDown=\"gemsJoinButState(this,'Down');\" onMouseUp=\"gemsJoinButState(this,'Idle');\"";
    var sRgtBut = "<img id="+sRgtName+"But name="+sRgtName+"But src=\""+oImgRgt.src+"\" border=\"0\" onClick=\"gemsJoinButClick(this);\" onLoad=\"gemsJoinButLoad(this);\" "+sHan;
    var sLftBut = "<img id="+sLftName+"But name="+sLftName+"But src=\""+oImgLft.src+"\" border=\"0\" onClick=\"gemsJoinButClick(this);\""+sHan;
    var sRet = "<table border=\"0\" class=\"joinBut\"><tr><td>"+sRgtBut+"</td></tr><tr><td>"+sLftBut+"</td></tr></table>";
    return sRet;
  }

}
GemsCtlJoin.prototype = new GemsCtl();

/* we do this here so that the bwooser has
 * time to construct the elements
 */
function gemsJoinButLoad(oImg) {
  var sName    = oImg.id.substring(3,oImg.id.length-3);
  var sLftName = "_L_" + oImg.id.substring(3,oImg.id.length-3);
  var sRgtName = "_R_" + sLftName.substring(3);
  gemsLog("sLftName="+sLftName+" rgt="+sRgtName+" name="+sName);
  var oRgtBut   = getElement(sLftName+"But");
  var oImgRgt = getElement("gemsImgRgtButIdle");
  if (oRgtBut.oToLB == null) {
    var oLftBut   = getElement(sRgtName+"But");
    var oLftLB    = getElement(sLftName);
    var oRgtLB    = getElement(sRgtName);
    var oForm     = oRgtLB.form;
    var oCtl      = makeGemsCtl(oForm,"Join",sName,"v","no title",null,true);
    oCtl.prebuilt();
    oRgtBut.oToLB = oLftLB;
    oRgtBut.oFrLB = oRgtLB;
    oLftBut.oToLB = oRgtLB;
    oLftBut.oFrLB = oLftLB;
    oLftLB.getValue  = gemsGetJoinValue;
    oRgtLB.getValue  = gemsGetJoinValue;
    oLftLB.getName   = gemsGetJoinName;
    oRgtLB.getName   = gemsGetJoinName;
    if (oLftLB.oGemCtl != null) {
      oLftLB.oGemCtl.sInitValue = oLftLB.oGemCtl.getCtlValue();
    }

  }
}

function gemsGetJoinValue(oCtl) {
  if (oCtl.name.substring(0,3) == "_R_") return '';
  var sVal = null;
  for(var i=0; i < oCtl.options.length; i++) {
    var oOpt = oCtl.options[i];
    if (sVal == null) {
      sVal = oOpt.value;
    } else {
      sVal += ";" + oOpt.value;
    }
  }
  //alert("Value "+oCtl.name+" is "+sVal);
  return sVal;
}

function gemsGetJoinName(oCtl) {
  return oCtl.name.substring(3);
}

function gemsJoinButState(oImg,sState) {
  var sDir = oImg.name.substring(1,2);
  var sImg = "gemsImg" + ((sDir == "R")?"Rgt":"Lft")+"But"+sState;
  var oImgNew = getElement(sImg);
  if (oImgNew != null) oImg.src = oImgNew.src;
  oImg.title = "";
  var sTitle = "";
  if (oImg.oFrLB != null) {
    var oOpts = oImg.oFrLB.options;
    for(var i=0; i < oOpts.length;i++) {
      var oOpt = oOpts[i];
      if (oOpt.selected) {
        if (oOpt.title != null) {
          sTitle += "\n" + oOpt.title;
        }
      }
    }
    if (sTitle.length > 1) oImg.title = sTitle.substring(1);
  }
}

function gemsJoinButClick(oImg) {
  var oOptions = oImg.oFrLB.options;
  var oMveOpts = new Array();
  for(var i=0; i < oOptions.length;i++) {
    var oOpt = oOptions[i];
    if (oOpt.selected) {
      oNewOpt = new Option();
      oNewOpt.text  = oOpt.text;
      oNewOpt.value = oOpt.value;
      oNewOpt.title = oOpt.title;
      oMveOpts[oMveOpts.length] = oNewOpt;
    }
  }
  for(var i=oOptions.length-1; i >= 0;i--) {
    oOpt = oOptions[i];
    if (oOpt.selected) {
      oOptions[i] = null;
    }
  }
  var oOldOpts = oImg.oToLB.options;
  var oNewOpts = new Array();
  var nMve = 0, nOld = 0;
  while(true) {
    var sMveKey = "~~~~";
    var sOldKey = "~~~~";
    if (nMve < oMveOpts.length) sMveKey = oMveOpts[nMve].text;
    if (nOld < oOldOpts.length) sOldKey = oOldOpts[nOld].text;
    if (sMveKey == sOldKey) break;
    if (sMveKey < sOldKey) {
      oNewOpts[oNewOpts.length] = oMveOpts[nMve++];
    } else {
      oNewOpts[oNewOpts.length] = oOldOpts[nOld++];
    }
  }
  oImg.oToLB.options.length = 0;
  for(var i=0; i < oNewOpts.length;i++) {
    oImg.oToLB.options[i] = oNewOpts[i];
  }
  var sVal
  if (oImg.name.substring(0,3) == "_R_") {
    sVal = oImg.oFrLB.getValue(oImg.oFrLB);
  } else {
    sVal = oImg.oToLB.getValue(oImg.oToLB);
  }
  //lwsLog("Value of "+oImg.name+" is "+sVal);
  var oForm = oImg.oToLB.form;
  gemsOnCtlSwitch(oForm,oImg.oToLB,"xfr");
  return true;
}

// -----------------------------------------------------------------
// --------------------     Gems Supplementary     -----------------
// -----------------------------------------------------------------


var nGemsRow = 0;
var nGemsOdd = 0;
/* Add rows/columns to a table.
 */
function gems2XTS(aCols) {
  var s = "<TR CLASS=\"tblEvenRow\">";
  nGemsRow++;
  if ((nGemsRow % 2) == 0) s = "<TR CLASS=\"tblOddRow\">";
  for(var i=0; i < aCols.length;i++) {
    var sTD = "<td>";
    var sCol = aCols[i];
    s += sTD+sCol+"</td>";
  }
  s +="</tr>";
  dw(s);
}

var oGEM = new Object();
function gems2XH(aCols) {
  var oGemHdr = new Object();
  oGEM.oGemHdr = oGemHdr;
  oGemHdr.aCols = new Array();
  oGEM.nRow = 0;
  oGEM.nOdd = 1;
  var s = "<TR CLASS=\"tblhead\" ALIGN=CENTER>"
  for(var i=0; i < aCols.length;i++) {
    var oFlds = aCols[i];
    var oCol = new Object();
    oGemHdr.aCols[oGemHdr.aCols.length] = oCol;
    oCol.sType    = oFlds[0];
    oCol.sName    = oFlds[1];
    oCol.sTitle   = oFlds[2];
    oCol.sParms   = oFlds[3];
    oCol.sWidth   = oFlds[4];
    oCol.nDataCol = i;
    var sTD = "<td>";
    var sName = oCol.sName;
    s += sTD+sName+"</td>";
  }
  s +="</tr>";
  dw(s);
}

function gems2XGetStrVal(sVal) {
  if (sVal == null) return "";
  return sVal;
}

function gems2XR(aCols) {
  var s = "<TR CLASS=\"tblEvenRow\">";
  var nOdd = oGEM.nOdd++;
  if ((nOdd % 2) == 0) s = "<TR CLASS=\"tblOddRow\">";
  dw(s);
  for(var i=0; i < oGEM.oGemHdr.aCols.length;i++) {
    var oCol = oGEM.oGemHdr.aCols[i];
    var sTD = "<td";
    if (oCol.sWidth != null) sTD += " style='width: "+oCol.sWidth+";'";
    var sTitle = oCol.sTitle;
    if ((sTitle != null) && (sTitle != "")) {
      sTD += " title=\""+sTitle+"\"";
    }
    sTD += ">";
    var sParms = oCol.sParms;
    var sValue = aCols[oCol.nDataCol];
    if (oCol.sType == "Select") {
      //if (sValue != null) sValue = "|;" + sValue.substring(1);
      if ((sParms == null) || (sParms == "")) sParms =  "style='width: 100%;'";
    } else {
      sTD += "&nbsp;";
    }
    dw(sTD);
    var sName = oCol.sName + oGEM.nRow + "_" + i;
    if (oCol.sType == "Link") {
      var oVals = new Array();
      var sParms = oCol.sParms;
      while(true) {
        var sReps = sParms.match(/%([0-9])/);
        if (sReps == null) break;
        sParms = sParms.replace(new RegExp(sReps[0],"g"),aCols[(sReps[1])-0]);
      }
      oVals[0] = sParms;
      oVals[1] = oCol.sName;
      makeGemsCtl(null,oCol.sType,sName,oVals,sTitle,null,false);
    } else {
      makeGemsCtl(null,oCol.sType,sName,sValue,sTitle,sParms,false);
    }
    if (oCol.sType != "Select") dw("&nbsp;");
    dw("</td>");
  }
  s ="</tr>";
  dw(s);
  oGEM.nRow++;
}

function gems2XTH(aCols) {
  nGemsRow = 0;
  nGemsOdd = 1;
  var s = "<TR CLASS=\"tblhead\" ALIGN=CENTER>"
  for(var i=0; i < aCols.length;i++) {
    var sCol = aCols[i];
    var sTD = "<td>";
    s += sTD+sCol+"</td>";
  }
  s +="</tr>";
  dw(s);
}


// See getFldAttr in DBWebObjs
// name/attrs/browseValidator
// Attrs:
//    nnn: size of text field
//    c: checkbox
//    j: joinbox  (many.many)
//    p: password
//    o: combobox (one.many)
//    R: readonly
//    r: required fld
//    t: textarea
function gems2XTU(sForm,sLab,sTitle,sFldAttr,sValue) {
  var oForm = getElement(sForm);
  var s = "<TR CLASS=\"tblEvenRow\">";
  var sAttrs = "";
  var sBV = null;
  var sFlds = sFldAttr.split("/");
  var sFld = sFlds[0];
  if (sFlds.length > 1) sAttrs = sFlds[1];
  if (sFlds.length > 2) sBV = sFlds[2];
  nGemsOdd++;
  if ((nGemsOdd % 2) == 0) s = "<TR CLASS=\"tblOddRow\">";
  var sTD = "<td>";
  var sTD1 = "<td>";
  if (sTitle.length > 0) {
    sTD1 = "<td title=\""+sTitle+"\">";
  }
  var sSize = "";
  var sType = "Text";
  var sBoxMsg = "boxMsg";
  for(var i=0; i < sAttrs.length;i++) {
    var c = sAttrs.charAt(i);
    if ((c < '0') || (c > '9')) break;
    sSize += sAttrs.substring(i,i+1);
  }
  if (sSize != "") {
    var nSize = parseInt(sSize);
    sSize = " maxlength=\""+sSize+"\"";
    if (nSize >= 64) {
      sType = "Textarea";
      //sBoxMsg = "boxMsgML";
    }
  }
  var sReadOnly = "";
  var sFldLab = "db"+sFld;
  var bRequired = false;
  if (sAttrs.indexOf("r") >= 0) bRequired = true;
  if (sAttrs.indexOf("p") >= 0) sType = "Password";
  if (sAttrs.indexOf("j") >= 0) sType = "Join";
  if (sAttrs.indexOf("t") >= 0) sType = "Textarea";
  if (sAttrs.indexOf("R") >= 0) {sReadOnly = " readonly "; sType = "";}
  if (sAttrs.indexOf("c") >= 0) {
    sType = "checkbox";
    var sChecked = "";
    sFldLab = "ck"+sFldLab.substring(2);
  }
  if (bRequired) sLab = "*"+sLab;
  s += sTD1+sLab+"</td>";
  var s2  = "";
  var oCtl = null;
  var sMsgBoxTxt = "";
  switch (sType) {
    case "Text":
    case "Textarea":
    case "Password":
      oCtl = makeGemsCtl(oForm,sType,sFldLab,sValue,sTitle,sSize,true);
      s2 = oCtl.getText();
      break;
    case "Join":
      var aRet = gemsParseJoinVals(sValue);
      oCtl = makeGemsCtl(oForm,sType,sFldLab,aRet,sTitle,sSize,true);
      s2 = oCtl.getText();
      break;
    default:
      s2  = "&nbsp;"+sType+"/"+sFldLab+"/"+sSize;
      break;
  }
  sMsgBoxTxt = gemsStdMsgBox(sBoxMsg,sFld);
  s += sTD+s2+"</td>";
  s += sTD+sMsgBoxTxt+"</td>";
  s +="</tr>";
  dw(s);
  if (oCtl != null) {
    oCtl.linkObjs();
    oCtl.bRequired = bRequired;
    oCtl.oBV = null;
    if (sBV != null) {
      oCtl.sBV = sBV;
      oCtl.oBV = new Function("oCtl","oForm","return "+sBV+";");
    }
  }
}

function gems2XStdMsgBox(sBoxMsg,sFld) {
  //var s = "<input class=\""+sBoxMsg+"\" type=\"text\" tabindex=\"-1\" readonly name=\"vf"+sFld+"\" value=\"\">";
  var s = "<span class="+sBoxMsg+" id=vf"+sFld+"></span>";
  return s;
}

function gems2XJoinMsgBox(sFld) {
  var s = "<table border=\"0\">";
  s += "<tr><td class=\"joinMsgHead\"></td></tr>"
  s += "<tr><td>"
  s += "<input class=\"joinMsg\" type=\"text\" tabindex=\"-1\" readonly name=\"vf"+sFld+"\" value=\"\">";
  s += "</td></tr>"
  s += "</table>";
  return s;
}

function gems2XParseJoinVals(sVals) {
  lwsLog("ParseJoin:"+sVals);
  var oTubs = makeTubFromStr(sVals);
  var aVals = oTubs.getValue("Tubs");
  var  aRet      = new Array();
  var  aLft      = new Array();
  var  aRgt      = new Array();
  aRet[0]        = aLft;
  aRet[1]        = aRgt;
  for(var i=0; i < aVals.length;i++) {
    var oTub    = aVals[i];
    var oA = new Array();
    oA[0] = oTub.getValue("T","");
    oA[1] = oTub.getValue("N","");
    oA[2] = oTub.getValue("D","");
    var sLft = oTub.getValue("V");
    if (sLft == "T") {
      aLft[aLft.length] = oA;
    } else {
      aRgt[aRgt.length] = oA;
    }
  }
  return aRet;
}







