  // This file contains PRIVATE functions that implement the dynamic GUI in the Advanced Selector widget.
   // They are not intended for use by anyone else and are subject to change without notice.
   
   // ------------------- define criterion data classes ----------------------
   function Criterion(types, auxTypes)
   {
      this.types = types;                 // an array of type ids
      this.auxTypes = auxTypes;           // an array of auxtype ids
      this.grps = new Array();            // an array of groups
      this.grpsCnt = 0;                   // number of valid (non-null) groups in criterion
      
      function setAuxTypes(auxTypes)
      {
         this.auxTypes = auxTypes;
      }
      this.setAuxTypes = setAuxTypes;     // function to set the aux types dynamically
   }
   
   function CriterionGroup(grpId, arrayOfRows, grpLop)
   {
      this.grpId   = grpId;               // id number for this group
      this.rows    = arrayOfRows;         // an array of criterion rows
      this.rowsCnt = arrayOfRows.length;  // number of valid (non-null) rows in this group
      this.grpLop  = grpLop;              // logical op to be used between this group and the next
      return this;
   }
   
   function CriteriaRow(rowId, crit, rowLop)
   {
      this.rowId  = rowId;                // id number for this row
      this.crit   = crit;                 // a criteria
      this.rowLop = rowLop;               // logical op to be used between this row and the next
      return this;
   }
   
   // a criterion row in a group
   function Criteria(attrib, op, attribVal)
   {
      this.attrib    = attrib;            // an attribute for the specified type (ds class)
      this.op        = op;                // op that makes sense for this attribute's syntax
      this.attribVal = attribVal;         // the value the user wants to search for
      return this;
   } 
   
   //----------------- utility methods to implement dynamic GUI ------------------------
   function isGrpIdxValid(grpIdx)
   {
      return (criterion!=null && criterion.grps!=null && 0<=grpIdx && grpIdx<criterion.grps.length);
   }
   
   function isRowIdxValid(grpIdx,rowIdx)
   {
      var isValid = false;
      if (isGrpIdxValid(grpIdx))
      {
         var grp = criterion.grps[grpIdx];
         isValid = (grp!=null && grp.rows!=null && 0<=rowIdx && rowIdx<grp.rows.length);
      }
      return isValid;
   }
   
   function isCriterionEmpty()
   {
      //criterion is considered empty if it has no groups
      //note: even if groups exists, we don't know if they contain valid data
      return (criterion==null || criterion.grps==null || criterion.grpsCnt<1);
   }
   
   function isGrpEmpty(grpIdx)
   {
      //group is considered empty if it has no rows
      //note: even if a group has rows, we don't know if the rows contain valid data
      var isEmpty = true;
      if (isGrpIdxValid(grpIdx) && criterion.grps[grpIdx]!=null)
      {
         var grp=criterion.grps[grpIdx];
         isEmpty = (grp==null || grp.rows==null || grp.rowsCnt<1);
      }
      return isEmpty;
   }
   
   function isRowEmpty(grp,rowIdx)
   {
      //row is considered empty if it has no attrib or op value in its crit object
      var isEmpty = true;
      if (grp!=null && grp.rows!=null && 0<=rowIdx && rowIdx<grp.rows.length)
      {
         var row = grp.rows[rowIdx];
         isEmpty = (row==null || row.crit==null ||
                    row.crit.attrib==null || row.crit.attrib.length==0 ||
                    row.crit.op==null || row.crit.op.length==0);
      }
      return isEmpty;
   }
   
   function isGrpValid(grpIdx)
   {
      //group is considered valid if at least one non-empty row exists in the group
      if (!isGrpEmpty(grpIdx))
      {
         //there are rows -- make sure at least one row contains valid data
         var grp = criterion.grps[grpIdx];
         
         //step though all rows in this group
         for (var j=0; j<grp.rows.length; j++)
         {
            if (!isRowEmpty(grp,j))
            {
               //we found a row with valid data
               return true;
            }
         }
      }
      return false;
   }
   
   function isCriterionValid()
   {
      //criterion is considered valid if one valid group exists
      if (!isCriterionEmpty())
      {
         //step through all groups
         for (var i=0; i<criterion.grps.length; i++)
         {
            if (isGrpValid(i))
            {
               return true;      //we found a group with valid data
            }
         }
         
         //group shouldn't be the only criteria. Possible to pass aux class ONLY
          var tmpDoc = parseXml(initialCriterionParm);
          
          //when <attribute><operator><value> is missing, this will be null initially
          if(tmpDoc == null)
          {
             return false;
          }
           
          var auxTypes = tmpDoc.getChild(XML_AUXTYPES);
          var aux_ids;
          
          if (auxTypes!=null)
          {
            aux_ids = auxTypes.getChildren(XML_ID);
            
            for (var i=0; i<aux_ids.length; i++)
            {
             //alert(iman.trim(aux_ids[i].getText()));
             
             if(iman.trim(aux_ids[i].getText()) != null)
             {
              return true;
             }
             
            }
          }
      }
      return false;  //criterion is not valid
   }
   
   function isLastGroup(grpIdx)
   {
      var last=true;
      if (isGrpIdxValid(grpIdx))
      {
         for (var i=grpIdx+1; i<criterion.grps.length; i++)
         {
            if (criterion.grps[i]!=null)
            {
               last=false;
               break;
            }
         }
      }
      return last;
   }
   
   function isLastRow(grpIdx, rowIdx)
   {
      var last=true;
      if (isRowIdxValid(grpIdx, rowIdx))
      {
         for (var i=rowIdx+1; i<criterion.grps[grpIdx].rows.length; i++)
         {
            if (criterion.grps[grpIdx].rows[i]!=null)
            {
               last=false;
               break;
            }
         }
      }
      return last;
   }
   
   function getGrpId(grpIdx)
   {  
      var ret = UNKNOWN_ID;
      if (isGrpIdxValid(grpIdx) && criterion.grps[grpIdx]!=null)
      {  
         ret = criterion.grps[grpIdx].grpId;
      }
      return ret;
   }
   
   function getRowId(grpIdx, rowIdx)
   {
      var ret = UNKNOWN_ID;
      if (isRowIdxValid(grpIdx, rowIdx))
      {
         ret = criterion.grps[grpIdx].rows[rowIdx].rowId;
      }
      return ret;
   }
   
   function getGrpLop()
   {
      var grpLop = LOP_OR;     // default row lop
      if (criterion!=null && criterion.grps!=null)
      {
         for (var i=0; i<criterion.grps.length; i++)
         {
            if (criterion.grps[i]!=null)
            {
               // first non-null row. use its lop for the group
               grpLop = criterion.grps[i].grpLop;
               break;
            }
         }
      }
      return grpLop;
   }
   
   function getRowLop(grpIdx)
   {
      var rowLop = LOP_AND;     // default row lop
      if (isGrpIdxValid(grpIdx))
      {
         var grp = criterion.grps[grpIdx];
         if (grp.rows!=null)
         {
            for (var i=0; i<grp.rows.length; i++)
            {
               if (grp.rows[i]!=null)
               {
                  // first non-null row. use its lop for the group
                  rowLop = grp.rows[i].rowLop;
                  break;
               }
            }
         }
      }
      return rowLop;
   }
   
   //----------------- methods to implement dynamic GUI ------------------------
   var ENABLED_COLOR = "white";
   var DISABLED_COLOR = "#EFEEE9";
   
   function addNewGroup()
   {
      // create a new group and add it to the criterion object
      var grpIdx = addGroup(getGrpLop());
      if (grpIdx!=-1)
      {
         addNewRow(grpIdx);  // add a blank row to this new group
      }
   }
   
   function addGroup(grpOp)
   {
      if (criterion!=null)
      {
         criterion.grpsCnt++;
         var grp = new CriterionGroup(criterion.grpsCnt, new Array(), grpOp);
         var grpIdx = criterion.grps.length;    // we'll add group to end of grps array
         criterion.grps[grpIdx] = grp;
         return grpIdx; 
      }
      return -1;
   }
   
   function deleteGroup(grpIdx)
   {
      // delete a group from the criterion array
      if (isGrpIdxValid(grpIdx))
      {
         criterion.grps[grpIdx] = null;
         criterion.grpsCnt--;
      }
      // if we have deleted last group, reinitialize criterion
      if (isCriterionEmpty())
      {
         createInitialCriterion();
      }
      
      // renumber the group ids
      renumberGroups();
   }
   
   function renumberGroups()
   {
      if (criterion!=null && criterion.grps!=null)
      {
         var grps = criterion.grps;
         var grpId = 0;
         for (var i=0; i<grps.length; i++)
         {
            if (grps[i]!=null)
            {
               // number the group
               grpId++;
               grps[i].grpId = grpId;
            }
         }
      }
   }
   
   function addNewRow(grpIdx)
   {
      //create a new, empty criteria row in the specified group
      addRow(grpIdx, "", "", "", getRowLop(grpIdx));
   }
    
   function addRow(grpIdx, attrib, op, value, rowOp)
   {
      if (isGrpIdxValid(grpIdx) && criterion.grps[grpIdx]!=null)
      {
         //we have a valid group
         var grp = criterion.grps[grpIdx];
         grp.rowsCnt++;
         var crit = new Criteria(attrib,op,value);
         var row = new CriteriaRow(grp.rowsCnt, crit, rowOp); 
         grp.rows[grp.rows.length] = row;
      }
   }
  
   function deleteRow(grpIdx, rowIdx)
   {
      if (isRowIdxValid(grpIdx, rowIdx))
      {
         criterion.grps[grpIdx].rows[rowIdx] = null;
         criterion.grps[grpIdx].rowsCnt--;
      }
      // if we have removed all rows in this group, remove group too
      if (isGrpEmpty(grpIdx))
      {
         deleteGroup(grpIdx);
      }
      
      // renumber the rows in the group
      renumberRows(grpIdx);
   }
   
   function renumberRows(grpIdx)
   {
      if (isGrpIdxValid(grpIdx) && criterion.grps[grpIdx]!=null)
      {
         var rows = criterion.grps[grpIdx].rows;
         if (rows!=null)
         {
            var rowId = 0;
            for (var i=0; i<rows.length; i++)
            {
               if (rows[i]!=null)
               {
                  // number the group
                  rowId++;
                  rows[i].rowId = rowId;
               }
            }
         }
      }
   }
  
   function changeAttribute(grpIdx, rowIdx, attribListName, opListName, valElementName)
   {
      var attrib = top.CritFrame.document.criterion.elements[attribListName].value;
      if (attrib!=null && isRowIdxValid(grpIdx, rowIdx))
      {
         var grp = criterion.grps[grpIdx];
         if (grp.rows[rowIdx]!=null && grp.rows[rowIdx].attrib != attrib)
         {
            //store the new attribute in the criterion structure
            if (attrib==ATTRIBUTE_LIST_TITLE)
            {
               grp.rows[rowIdx].crit.attrib = "";
            }
            else
            {
               grp.rows[rowIdx].crit.attrib = attrib;
            }
            
            //update the corresponding ops array
            var opList = top.CritFrame.document.criterion.elements[opListName];
            if (opList!=null)
            {
               //clear out all but the '[op]' entry in the list
               opList.options.length=1;
               setOp(grpIdx, rowIdx, "");
               
               if (attrib!=ATTRIBUTE_LIST_TITLE)
               {
                  var opArray = m_attribOps[attrib];
                  if (opArray==null)
                  {
                     //get the default ops
                     opArray=m_attribOps[DEFAULT_OPS];
                  }
                  if (opArray!=null)
                  {
                     //add the new entries
                     for (var i=0; i<opArray.length; i++)
                     {
                        opList.options[opList.options.length] = new Option(m_displayOps[opArray[i]],opArray[i]);
                     }      
                  }
               }
            }
            
            //update the corresponding value array/entry
            var valElement = top.CritFrame.document.criterion.elements[valElementName];
            setAttribValue(grpIdx, rowIdx, "");
            setValueInputState(valElement, true);    //enable the value input field
            if (valElement!=null)
            {
               //get array of know values, if it exists
               var valArray = m_attribVals[attrib];
               var textVis   = valElement[0].style.display;
               var selectVis = valElement[1].style.display;
               
               //clear attribValue element 
               valElement[0].value="";
               valElement[1].options.length=1;
               if (valArray!=null)
               {
                  for (var i=0; i<valArray.length; i++)
                  {
                     valElement[1].options[valElement[1].options.length] = new Option(valArray[i], valArray[i]);
                  }
               }
               
               //see what mode we need, text or select
               var newAttribIsText = (valArray==null);
               if (newAttribIsText && textVis=="none")
               {
                  //we need to switch visibility to text mode
                  valElement[0].style.display="inline";  //make text input visible
                  valElement[1].style.display="none";    //make select input invisible
               } 
               else if (!newAttribIsText && selectVis=="none")
               {
                  //we need to switch visibility to select mode
                  valElement[0].style.display="none";    //make text input invisible
                  valElement[1].style.display="inline";  //make select input visible
               } 
            }
         }
      }
   }
   
   function setValueInputState(el, bEnabled)
   {
      el[0].disabled = !bEnabled;
      el[1].disabled = !bEnabled;
      el[0].style.backgroundColor = ((bEnabled)?ENABLED_COLOR:DISABLED_COLOR);
      el[1].style.backgroundColor = ((bEnabled)?ENABLED_COLOR:DISABLED_COLOR);
   }
   
   function setOp(grpIdx, rowIdx, op)
   {
      if (isRowIdxValid(grpIdx, rowIdx))
      {
         var grp = criterion.grps[grpIdx];
         if (grp.rows[rowIdx]!=null)
         {
            if (op==OP_LIST_TITLE)
            {
               op="";
            }
            grp.rows[rowIdx].crit.op = op;
         }
      }
   }
   
   function changeOp(grpIdx, rowIdx, opListName)
   {
      var op = top.CritFrame.document.criterion.elements[opListName].value;
      if (op!=null)
      {
         detectOperatorTypeChange(grpIdx, rowIdx, opListName, op);
         setOp(grpIdx, rowIdx, op);
      }
   }
   
   //handles gui changes required if the user selects an operator which has a different
   //number of operands than the previous operator (ie, binary to unary; unary to binary)
   function detectOperatorTypeChange(grpIdx, rowIdx, opListName, op)
   {
      if (isRowIdxValid(grpIdx, rowIdx))
      {
         var grp = criterion.grps[grpIdx];
         var oldOp = grp.rows[rowIdx].crit.op;
         //see if operator operand count is changing
         if (m_uniOps[oldOp]!=m_uniOps[op])
         {
            //it changed; get value element
            var rowId = getRowId(grpIdx, rowIdx);
            var grpId = getGrpId(grpIdx);
            var inputName = "grp"+grpId+"crit"+rowId;
            var el = top.CritFrame.document.criterion.elements[inputName];
            
            //clear value
            el[0].value="";
            el[1].options[0].selected=true;
            setAttribValue(grpIdx, rowIdx, ""); //clear attribute value
            
            //enable or disable value input
            setValueInputState(el, !(m_uniOps[op]==true));
         }
      }
   }
   
   function setAttribValue(grpIdx, rowIdx, attribVal)
   {
      if (isRowIdxValid(grpIdx, rowIdx))
      {
         var grp = criterion.grps[grpIdx];
         if (grp.rows[rowIdx]!=null)
         {
            if (attribVal==VALUE_LIST_TITLE)
            {
               attribVal="";
            }
            grp.rows[rowIdx].crit.attribVal = attribVal;
         }
      }
   }
   
   function changeAttributeValue(grpIdx, rowIdx, inputName)
   {
      var element = top.CritFrame.document.criterion.elements[inputName];
      if (element!=null)
      {
         if (element[0].style.display=="inline")
         {
            setAttribValue(grpIdx, rowIdx, element[0].value);
         }
         else if (element[1].style.display=="inline")
         {
            setAttribValue(grpIdx, rowIdx, element[1].value);
         }
      }
   }
   
   function rowLopChange(grpIdx)
   {
      if (isGrpIdxValid(grpIdx))
      {
         // get the new operation
         var newLop = LOP_NOP;
         var grp = criterion.grps[grpIdx];
         var grpId = getGrpId(grpIdx);
         var grpLopList = top.CritFrame.document.criterion.elements["grp"+grpId+"lop1"];
         if (grpLopList!=null)
         {
            var newLop = grpLopList.value;
            if (newLop==null)
            {
               newLop=LOP_NOP;
            }
         }
         
         // set the operations on all other rows in this group
         if (grp.rows!=null)
         {
            for (var i=0; i<criterion.grps[grpIdx].rows.length; i++)
            {
               var row = grp.rows[i];
               if (row!=null)
               {
                  // set the operation in the criterion structure
                  row.rowLop = newLop;
                  
                  // set the operation on the form    
                  var rowId = getRowId(grpIdx,i);
                  if (!isLastRow(grpIdx,i) && rowId!=UNKNOWN_ID && rowId!=1)
                  {
                     grpLopList =  top.CritFrame.document.criterion.elements["grp"+grpId+"lop"+rowId];
                     if (grpLopList!=null)
                     {
                        grpLopList.value=" "+newLop;
                     }
                  }
               }
            }
         }
      }
   }
   
   function groupLopChange()
   {
      if (criterion!=null && criterion.grps!=null)
      {
         // get the new operation
         var newLop = LOP_NOP;
         var lopList = top.CritFrame.document.criterion.elements["grplop1"];
         if (lopList!=null)
         {
            var newLop = lopList.value;
            if (newLop==null)
            {
               newLop=LOP_NOP;
            }
         }
         
         // set the operations on all groups
         for (var i=0; i<criterion.grps.length; i++)
         {
            var grp=criterion.grps[i];
            if (grp!=null)
            {
               // set the operation in the criterion structure
               grp.grpLop = newLop;
               
               // set the operation on the form    
               var grpId = getGrpId(i);
               if (!isLastGroup(i) && grpId!="?" && grpId!=1)
               {
                  lopList =  top.CritFrame.document.getElementById("grplop"+grpId);
                  if (lopList!=null)
                  {
                     lopList.innerText=newLop;
                  }
               }
            }
         }
      }
   }
   
   //----------------- methods to output html --------------------------
   function displayCriterion()
   {
      writeBegin();  //write out header html
      
      //write out content based on criterion array
      for (var i=0; i<criterion.grps.length; i++)
      {
         if (criterion.grps[i]!= null)
         {
            writeGroupHeader(i);
            writeGroup(i);
            if (!isLastGroup(i))
            {
               writeGroupLogicalOp(i, criterion.grps[i].grpLop);
            }
         }
      }
      
      writeEnd();    //write out trailing html
      
      //programmatically populate attribute select options
      setAttributeOptions();     
   }
   
   function writeBegin()
   {
      top.CritFrame.document.open();
      top.CritFrame.document.writeln('<HTML><HEAD><TITLE>AdvSelCriterion</TITLE>');
      top.CritFrame.document.writeln('<style type= "text/css" media="screen">');
      top.CritFrame.document.writeln('	.bodytext  {color:#6F6660; font-size: 12px; font-family: "Trebuchet MS", Arial, Helvetica, Geneva, Swiss, SunSans-Regular;');
      top.CritFrame.document.writeln('</style>');
      top.CritFrame.document.writeln('</HEAD>');
      top.CritFrame.document.writeln('<body bgcolor="#DFDDD5" onLoad="javascript:top.criterionLoaded=true">');
      top.CritFrame.document.writeln('<div style="height:100%; overflow:auto;">');
      top.CritFrame.document.writeln('<form name=criterion onSubmit="javascript:return false">');
      top.CritFrame.document.writeln('<table style="background:#BFBBB2;" width=100% border=1" cellpadding=5 cellspacing=0>');
      top.CritFrame.document.writeln('<tr>');
      top.CritFrame.document.writeln('   <!-- selection criterion groups -->');
      top.CritFrame.document.writeln('   <td>');
      top.CritFrame.document.writeln('      <table style="background:#BFBBB2" width=100% border=0 cellpadding=2 cellspacing=0>');
      top.CritFrame.document.writeln('      <tr style="background:#BFBBB2;height:3"><td style="font-size:0px">&nbsp;</td></tr>');
      top.CritFrame.document.writeln('');
   }
   
   function writeEnd()
   {         
      top.CritFrame.document.writeln('      <tr style="background:#BFBBB2;height:3"><td style="font-size:0px"colspan=5>&nbsp;</td></tr>');
      top.CritFrame.document.writeln('      </table>');
      top.CritFrame.document.writeln('   </td>');
      top.CritFrame.document.writeln('');   
      top.CritFrame.document.writeln('   <!-- operations on all groups -->');
      top.CritFrame.document.writeln('   <td style="width:16px; height:100%">');
      top.CritFrame.document.writeln('      <table style="background:#BFBBB2; height:100%" border=0" cellpadding=0 cellspacing=0>');
      top.CritFrame.document.writeln('      <tr>');
      top.CritFrame.document.writeln('         <td align=right valign=top>');
      top.CritFrame.document.writeln('         <img alt="'+toDisplay(CLEAR_CRITERION)+'" src="'+NPS_MODULES_URL+'/dev/images/'+MINUS_BUTTON+'" border=0 onClick="javascript:top.createInitialCriterion();top.displayCriterion()">');
      top.CritFrame.document.writeln('         </td>');
      top.CritFrame.document.writeln('      </tr>');
      top.CritFrame.document.writeln('');      
      top.CritFrame.document.writeln('      <tr><td>&nbsp;</td></tr>');
      top.CritFrame.document.writeln('');      
      top.CritFrame.document.writeln('      <tr>');
      top.CritFrame.document.writeln('         <td align=right valign=bottom>');
      top.CritFrame.document.writeln('         <img alt="'+toDisplay(ADD_GROUP)+'" src="'+NPS_MODULES_URL+'/dev/images/'+PLUS_BUTTON+'" border=0 onClick="javascript:top.addNewGroup();top.displayCriterion()">');
      top.CritFrame.document.writeln('         </td>');
      top.CritFrame.document.writeln('      </tr>');
      top.CritFrame.document.writeln('      </table>');  
      top.CritFrame.document.writeln('   </td>');
      top.CritFrame.document.writeln('</tr>');
      top.CritFrame.document.writeln('</table>');
      top.CritFrame.document.writeln('</form>');
      top.CritFrame.document.writeln('</div>');
      top.CritFrame.document.writeln('</body>');
      top.CritFrame.document.writeln('</HTML>');
      top.CritFrame.document.close();
   }
   
   function writeGroupHeader(grpIdx)
   {  
      var grpId = getGrpId(grpIdx);
      
      top.CritFrame.document.writeln('      <!-- group '+grpId+' header -->');
      top.CritFrame.document.writeln('      <tr height=8 bgcolor="#BFBBB2">');
      top.CritFrame.document.writeln('         <!-- group '+grpId+' logical operation -->');
      
      if (grpId==1)
      {
         top.CritFrame.document.writeln('         <td bgcolor="#DFDDD5" width=16 align=center><img alt="'+toDisplay(REMOVE_GROUP)+'" src="'+NPS_MODULES_URL+'/dev/images/'+MINUS_BUTTON+'" border=0 onClick="javascript:top.deleteGroup('+grpIdx+');top.displayCriterion()"></td>');
         top.CritFrame.document.writeln('         <td align=left bgcolor="#DFDDD5" class="bodytext"><b><nobr>&nbsp;'+toDisplay(LOGIC_GROUP_TITLE)+' '+grpId+'</b></nobr></td>');
         top.CritFrame.document.writeln('         <td align=center bgcolor="#BFBBB2">&nbsp;</td>');
      }
      else
      {
         top.CritFrame.document.writeln('         <td bgcolor="#DFDDD5" align=center height=8 width=16><img alt="'+toDisplay(REMOVE_GROUP)+'" src="'+NPS_MODULES_URL+'/dev/images/'+MINUS_BUTTON+'" border=0 onClick="javascript:top.deleteGroup('+grpIdx+');top.displayCriterion()"></td>');
         top.CritFrame.document.writeln('         <td align=left bgcolor="#DFDDD5" class="bodytext"><b><nobr>&nbsp;'+toDisplay(LOGIC_GROUP_TITLE)+' '+grpId+'</nobr></b></td>');
      }
      
      top.CritFrame.document.writeln('         <td align=center bgcolor="#BFBBB2">&nbsp;</td>');
      top.CritFrame.document.writeln('         <td align=center bgcolor="#BFBBB2">&nbsp;</td>');
      top.CritFrame.document.writeln('      </tr>');
      top.CritFrame.document.writeln('');    
   }
   
   function writeGroup(grpIdx)
   {
      if (isGrpIdxValid(grpIdx) && criterion.grps[grpIdx]!=null)
      {
         var grp = criterion.grps[grpIdx];
         for (var i=0; i<grp.rows.length; i++)
         {
            if (grp.rows[i]!=null)
            {
               // write out this row  
               writeRow(grpIdx, i, grp.rows[i]);
            }
         }
      }
   }
   
   function writeRow(grpIdx, rowIdx, row)
   {
      var rowId = getRowId(grpIdx, rowIdx);
      var grpId = getGrpId(grpIdx);
      
      var crit = row.crit;
      var attrib = crit.attrib;
      var op = crit.op;
      var attribVal = crit.attribVal;
      var rowLop = row.rowLop;
      
      //write group criterion header
      top.CritFrame.document.writeln('      <!-- group '+grpId+' row '+rowId+' selection criterion -->');
      top.CritFrame.document.writeln('      <tr bgcolor="#EFEEE9">');
      top.CritFrame.document.writeln('         <td align=center width=16><img alt="'+toDisplay(REMOVE_ROW)+'" src="'+NPS_MODULES_URL+'/dev/images/'+MINUS_BUTTON+'" border=0 onClick="javascript:top.deleteRow('+grpIdx+','+rowIdx+');top.displayCriterion()"></td>');
      
      //write out the selection list of known attributes
      writeKnownAttributes(grpIdx, rowIdx, attrib);    
      
      //write out the selection list of know operation for the attribute attrib
      writeKnownAttributeOps(grpIdx, rowIdx, attrib, op);
      
      //write out the current attribute value
      writeKnownAttributeValues(grpIdx, rowIdx, attrib, op, attribVal);
      
      //write out row logical op or add row button
      if (isLastRow(grpIdx, rowIdx))
      {
         //write add row button
         top.CritFrame.document.writeln('         <td align=center width=16><img alt="'+toDisplay(ADD_ROW)+'" src="'+NPS_MODULES_URL+'/dev/images/'+PLUS_BUTTON+'" border=0 onClick="javascript:top.addNewRow('+grpIdx+');top.displayCriterion()"></td>');
      }
      else
      {
         //write row logical op
         writeRowLogicalOp(grpIdx, rowIdx, rowLop);
      }
      
      top.CritFrame.document.writeln('      </tr>');
      top.CritFrame.document.writeln('');
   }
   
   function writeKnownAttributes(grpIdx, rowIdx, attrib)
   {
      var rowId = getRowId(grpIdx, rowIdx);
      var grpId = getGrpId(grpIdx);
      var attribListName = "grp"+grpId+"attr"+rowId;
      var opListName = "grp"+grpId+"op"+rowId;
      var valElementName = "grp"+grpId+"crit"+rowId;
      
      top.CritFrame.document.writeln('         <td style="width='+aPercent+'%" align=center><select style="width:100%" name='+attribListName+' onChange="javascript:top.changeAttribute('+grpIdx+','+rowIdx+',\''+attribListName+'\',\''+opListName+'\',\''+valElementName+'\')">');
      top.CritFrame.document.writeln('         <option value="'+toDisplay(ATTRIBUTE_LIST_TITLE)+'">'+toDisplay(ATTRIBUTE_LIST_TITLE));
      top.CritFrame.document.writeln('         </select></td>');
   }
   
   function writeKnownAttributeOps(grpIdx, rowIdx, attrib, op)
   {
      var rowId = getRowId(grpIdx, rowIdx);
      var grpId = getGrpId(grpIdx);
      var opArray = m_attribOps[attrib];
      var listName = "grp"+grpId+"op"+rowId;
     
      top.CritFrame.document.writeln('         <td style="width='+oPercent+'%" align=center><select style="width:100%" name='+listName+' onChange="javascript:top.changeOp('+grpIdx+','+rowIdx+',\''+listName+'\')">');
      top.CritFrame.document.writeln('         <option value="'+toDisplay(OP_LIST_TITLE)+'">'+toDisplay(OP_LIST_TITLE));
      if (attrib!=null && attrib.length!=0)
      {
         //output op selections
         if (opArray==null)
         {
            opArray = m_attribOps[DEFAULT_OPS];
         }
         if (opArray!=null && opArray.length>0)
         {
            for (var i=0; i<opArray.length; i++)
            {
               if (op != opArray[i])
               {
                  top.CritFrame.document.writeln('         <option value="'+opArray[i]+'">'+m_displayOps[opArray[i]]);
               }
               else
               {
                  top.CritFrame.document.writeln('         <option value="'+opArray[i]+'" SELECTED>'+m_displayOps[opArray[i]]);
               }
            }
         }
      }
      top.CritFrame.document.writeln('         </select></td>');
   }
   
   function writeKnownAttributeValues(grpIdx, rowIdx, attrib, op, attribVal)
   {
      var rowId = getRowId(grpIdx, rowIdx);
      var grpId = getGrpId(grpIdx);
      var valArray = m_attribVals[attrib];
      var bDisabled = (m_uniOps[op]==true);
      var disabled = ((bDisabled)?"disabled ":"");
      var bgColor = ((bDisabled)?DISABLED_COLOR:ENABLED_COLOR);
      var inputName = "grp"+grpId+"crit"+rowId;
      var textVis = "none";
      var selectVis = "inline";
     
      if (valArray==null)
      {
         //just a text field
         textVis="inline";
         selectVis="none";
      } 
      
      //output value HTML elements
      top.CritFrame.document.writeln('         <td width='+iWidth+' align=center>');
      top.CritFrame.document.writeln('         <span><input '+disabled+'style="width:'+iWidth+'; display:'+textVis+'; background-color:'+bgColor+'" type=text name='+inputName+' value="'+attribVal+'" onChange="javascript:top.changeAttributeValue('+grpIdx+','+rowIdx+',\''+inputName+'\')"></span>');
      
      top.CritFrame.document.writeln('         <span><select '+disabled+'style="width:'+iWidth+'; display:'+selectVis+'; background-color:'+bgColor+'" name='+inputName+' onChange="javascript:top.changeAttributeValue('+grpIdx+','+rowIdx+',\''+inputName+'\')">');
      top.CritFrame.document.writeln('         <option value="'+toDisplay(VALUE_LIST_TITLE)+'">'+toDisplay(VALUE_LIST_TITLE));
      if (valArray!=null && valArray.length>0)
      {
         for (var i=0; i<valArray.length; i++)
         {
            if (attribVal != valArray[i])
            {
               top.CritFrame.document.writeln('         <option value="'+valArray[i]+'">'+valArray[i]);
            }
            else
            {
               top.CritFrame.document.writeln('         <option value="'+valArray[i]+'" SELECTED>'+valArray[i]);
            }
         }
      }
      top.CritFrame.document.writeln('         </select></span>');
      
      top.CritFrame.document.writeln('         </td>');
   }
   
   function writeRowLogicalOp(grpIdx, rowIdx, rowLop)
   { 
      var rowId = getRowId(grpIdx, rowIdx);
      var grpId = getGrpId(grpIdx);
      
      if (rowId==1)
      {
         //output a and/or selection list
         top.CritFrame.document.writeln('         <td align=center><select class="bodytext" style="background:#EFEEE9; width:55" name=grp'+grpId+'lop'+rowId+' onChange="javascript:top.rowLopChange('+grpIdx+')">');
         if (rowLop!=null && rowLop==LOP_OR)
         {
            // or is selected
            top.CritFrame.document.writeln('         <option value="'+toDisplay(LOP_AND)+'">'+toDisplay(LOP_AND));
            top.CritFrame.document.writeln('         <option value="'+toDisplay(LOP_OR)+'" SELECTED>'+toDisplay(LOP_OR));
         }
         else
         {
            // and is selected
            top.CritFrame.document.writeln('         <option value="'+toDisplay(LOP_AND)+'" SELECTED>'+toDisplay(LOP_AND));
            top.CritFrame.document.writeln('         <option value="'+toDisplay(LOP_OR)+'">'+toDisplay(LOP_OR));
         }
         top.CritFrame.document.writeln('         </select></td>');
      }
      else
      {
         //just print and/or in a readonly text field
         if (rowLop==null || rowLop!=LOP_OR)
         {
            rowLop=LOP_AND;   //default is 'and'
         }
         top.CritFrame.document.writeln('         <td width=16 align=center><input name=grp'+grpId+'lop'+rowId+' class=bodytext style="background:EFEEE9; border:0" type=text readonly align=middle size=3 value=" '+rowLop+'"</td>');
      }
   }
   
   function writeGroupLogicalOp(grpIdx, grpLop)
   {  
      var grpId = getGrpId(grpIdx);
      
      top.CritFrame.document.writeln('      <!-- inter-group logical operation -->');
      if (grpId==1)
      {
         //output  and/or selection list
         top.CritFrame.document.writeln('      <tr style="height:10">');
         top.CritFrame.document.writeln('         <td style="background:#BFBBB2"style="font-size:0px" colspan=2>&nbsp;</td>');
         top.CritFrame.document.writeln('         <td rowspan=2 valign=middle align=right><select class="bodytext" style="width:55; background:#BFBBB2; font-weight:bold" name=grplop'+grpId+' onChange="javascript:top.groupLopChange()">');

         if (grpLop!=null && grpLop==LOP_AND)
         {
            // and is selected
            top.CritFrame.document.writeln('         <option value="'+toDisplay(LOP_AND)+'" SELECTED>'+toDisplay(LOP_AND));
            top.CritFrame.document.writeln('         <option value="'+toDisplay(LOP_OR)+'">'+toDisplay(LOP_OR));
         }
         else
         {
            // or is selected (default)
            top.CritFrame.document.writeln('         <option value="'+toDisplay(LOP_AND)+'">'+toDisplay(LOP_AND));
            top.CritFrame.document.writeln('         <option value="'+toDisplay(LOP_OR)+'" SELECTED>'+toDisplay(LOP_OR));
         }
         top.CritFrame.document.writeln('         </select></td>');
         top.CritFrame.document.writeln('         <td style="background:#BFBBB2"style="font-size:0px" colspan=2>&nbsp;</td>');
         top.CritFrame.document.writeln('      </tr>');
         top.CritFrame.document.writeln('');     
      }
      else
      {
         //just print and/or in a readonly text field
         top.CritFrame.document.writeln('      <tr style="height:10">');
         top.CritFrame.document.writeln('         <td style="background:#BFBBB2"style="font-size:0px" colspan=2>&nbsp;</td>');

         if (grpLop==null || grpLop!=LOP_AND)
         {
            grpLop=LOP_OR;   //default is 'or'
         }
         
         top.CritFrame.document.writeln('         <td valign=middle align=right rowspan=2 class="bodytext" style="background:#BFBBB2; font-weight:bold"><div id=grplop'+grpId+' style="text-align:left; width:50">'+grpLop+'</div></td>');
         top.CritFrame.document.writeln('         <td style="background:#BFBBB2"style="font-size:0px" colspan=2>&nbsp;</td>');
         top.CritFrame.document.writeln('      </tr>');
      }
   }
   
   function setAttributeOptions()
   {
      //walk through all the groups
      for (var grpIdx=0; grpIdx<criterion.grps.length; grpIdx++)
      {
         if (isGrpIdxValid(grpIdx) && criterion.grps[grpIdx]!=null)
         {
            var grp = criterion.grps[grpIdx];
            
            //walk through all the rows in this group
            for (var rowIdx=0; rowIdx<grp.rows.length; rowIdx++)
            {
               var row = grp.rows[rowIdx];
               if (row!=null)
               {
                  var attribListName = "grp"+getGrpId(grpIdx)+"attr"+getRowId(grpIdx, rowIdx);
                  var attrib = row.crit.attrib;
                  
                  var list = top.CritFrame.document.criterion.elements[attribListName]
                  var match=0;
                  if (list!=null)
                  {
                     //step through the attributes creating new select options
                     for (var i=0; i<m_attribs.length; i++)
                     {
                        var len = list.options.length;
                        list.options[len] = new Option(m_attribs[i], m_attribs[i]);
                        if (attrib == m_attribs[i])
                        { 
                           match=len;
                        }
                     }
                  }
                  list.selectedIndex=match;  //set selected object
               }
            }
         }
      }
   }
   
   var winW=null;
   var winH=null;
   var wrt = null;
   
   function checkSizeLater()
   {
      //remember current size
      winW=document.body.offsetWidth;
      winH=document.body.offsetHeight;
            
      //look again in 1/20 sec
      wrt = setTimeout("windowResize(1)",50); 
   }
    
   function windowResize(src)
   {
      if (src==0)
      {
         //called from the onResize event
         if (wrt==null)
         {
            //must be first call.  remember size
            checkSizeLater();
         }
      }
      //called by itself
      else if (winW!=document.body.offsetWidth &&
               winH!=document.body.offsetHeight)
      {
         //different size since the last time we looked.  look later
         checkSizeLater()
      }
      else
      {
         //resizing hasn't occured recently, assume done.
         wrt=null;
         iWidth = (document.body.offsetWidth-24-32)*vPercent/100;
         setAttributeValueWidths();
      }
   }
   
   function setAttributeValueWidths()
   {
     //walk through all the groups
      for (var grpIdx=0; grpIdx<criterion.grps.length; grpIdx++)
      {
         if (isGrpIdxValid(grpIdx) && criterion.grps[grpIdx]!=null)
         {
            var grp = criterion.grps[grpIdx];
            
            //walk through all the rows in this group
            var grpId = getGrpId(grpIdx);
            for (var rowIdx=0; rowIdx<grp.rows.length; rowIdx++)
            {
               var row = grp.rows[rowIdx];
               if (row!=null)
               {
                  var valElementName = "grp"+grpId+"crit"+getRowId(grpIdx, rowIdx);
                  var valElement = top.CritFrame.document.criterion.elements[valElementName];
                  if (valElement!=null)
                  {
                     valElement[0].style.width=iWidth;
                     valElement[1].style.width=iWidth;
                  }
               }
            }
         }
      }
   }
   
   //----------------- methods to initialize criterion ---------------------------
   function createEmptyCriterion()
   {
      criterion = new Criterion(m_typeIds, m_auxTypeIds);
   }
   
   function createInitialCriterion()
   {
      createEmptyCriterion();
      addNewGroup();
   }
   
   function setupInitialCriterion()
   { 
      if (initialCriterionParm!=null && initialCriterionParm.length>0)
      {
         readInitialCriterion();    //read in the initial criterion
      }
      else
      {
         createInitialCriterion();
      }
   }
   
   //----------------- methods to import initial criterion ----------------------
   function readInitialCriterion()
   {
      //create an empty Criterion structure
      createEmptyCriterion();
      
      if (initialCriterionParm!=null && initialCriterionParm.length>0)
      {
         var xml = parseXml(initialCriterionParm);        //parse into an xml doc
         var grps=null;
         var icTypeIds = new Array();
         var icAuxTypeIds = new Array();
         
         //read in types and auxtype info from xml criterion
         readTypesAuxTypesInfo(xml, icTypeIds, icAuxTypeIds)
         var bValidTypeInfo = false;
         
         //if no OS.Types parameter was provided; but info is available in xml, use xml info
         if (m_typeIds.length==0 && icTypeIds.length>0)
         {
            //use the info from the xml
            m_typeIds = icTypeIds;
            m_auxTypeIds = icAuxTypeIds;
            bValidTypeInfo = true;
         } 
         else
         {
            //make sure info from parameters is consistent with info in initial criterion
            bValidTypeInfo = isTypeInfoValid(icTypeIds, icAuxTypeIds);
         }
         
         if (!bValidTypeInfo)
         {
            grps = new Array();  //since type info is invalid, assume no groups
         }
         else
         {
            //criterion is for this type -- get the group definitions
            grps = xml.getChildren(XML_GROUP);
         } 
          
         //step through groups
         for (var i=0; i<grps.length; i++)
         {
            var grp = grps[i];   //xml element description of one group
            if (grp!=null)
            {
               var rowAdded=false;
               var rows   = grp.getChildren(XML_ROW);
               var grpLop = grp.getChildTextTrim(XML_GRPLOP);
               //add this group to the criterion only if rows and grpLop are valid
               if (rows!=null && rows.length>0 && grpLop!=null && (grpLop==LOP_AND || grpLop==LOP_OR))
               {
                  var grpIdx = addGroup(grpLop);
                     
                  //step through rows
                  for (var j=0; j<rows.length; j++)
                  {
                     var attrib = rows[j].getChildTextTrim(XML_ATTRIB);
                     var op     = rows[j].getChildTextTrim(XML_OP);
                     var value  = rows[j].getChildTextTrim(XML_VALUE);
                     var rowLop = rows[j].getChildTextTrim(XML_ROWLOP);
                     if (attrib!=null && op!=null && value!=null && rowLop!=null && (rowLop==LOP_AND || rowLop==LOP_OR))
                     {
                        //add this row
                        addRow(grpIdx, attrib, op, value, rowLop);
                        rowAdded=true;
                     }
                  }
                  
                  //done stepping through rows.  make sure we have at least one valid row
                  if (!rowAdded)
                  {
                     deleteGroup(grpIdx); //group is empty, remove it
                  }
               }
            }
         } 
        }
      
      // make sure we we added at least one valid group
      if (isCriterionEmpty())
      {
         //no groups, create criterion with one blank row
         createInitialCriterion();
      }
   }
   
   function readTypesAuxTypesInfo(xmlDoc, icTypeIds, icAuxTypeIds)
   {
      if(MS55 && xmlDoc ==null)
       return;
       
      var types = xmlDoc.getChild(XML_TYPES);
      var ids = null;
      if (types!=null)
      {
         ids = types.getChildren(XML_ID);
         for (var i=0; i<ids.length; i++)
         {
            icTypeIds[i] = iman.trim(ids[i].getText());
         }
      }
      var auxTypes = xmlDoc.getChild(XML_AUXTYPES);
      if (auxTypes!=null)
      {
         ids = auxTypes.getChildren(XML_ID);
         for (var i=0; i<ids.length; i++)
         {
            icAuxTypeIds[i] = iman.trim(ids[i].getText());
            
         }
      }
   }
   
   //make sure this initial criterion xml is for the current types and auxtypes
   function isTypeInfoValid(icTypeIds, icAuxTypeIds)
   {
      //make sure we have type info to check
      if (m_typeIds==null && icTypeIds==null)
      {
         return false;  //invalid, no type info OS.Types param or in xml
      }
   
      //make sure every type from the OS.Types param is found in the xml criterion
      //there may be more xml types, but that's ok
      for (var i=0; i<m_typeIds.length; i++)
      {
         var id = m_typeIds[i].toLowerCase();
         var bFound = false;
         for (var j=0; j<icTypeIds.length; j++)
         {
            if (id == icTypeIds[j].toLowerCase())
            {
               bFound = true;
               break;
            }
         }
         if (!bFound)
         {
            //OS.Type not found in xml, return invalid
            return false;
         }
      }
      
      var bHaveAuxTypes = (m_auxTypeIds!=null && m_auxTypeIds.length>0); //aux types from OS.AuxTypes param?
      var bHaveXmlAuxTypes = (icAuxTypeIds!=null && icAuxTypeIds.length>0);  //aux types in xml?
      
      //its ok if no auxType info is present in both locations
      if (!bHaveAuxTypes && !bHaveXmlAuxTypes)
      {
         return true;   //valid, we have no auxType in xml or OS.AuxTypes
      }
      
      //its bad if we have it in one location but not the other
      //make sure we have aux type info in xml if it exists in OS.AuxTypes param
      if ( (bHaveAuxTypes && !bHaveXmlAuxTypes) ||
           (!bHaveAuxTypes && bHaveXmlAuxTypes) )
      {
         return false;  //invalid -- if you have one, you must have the other
      } 
      
      //since we have both, they had better exactly match
      if (!isEqual(icAuxTypeIds, m_auxTypeIds))
      {
         return false;  //invalid, OS.AuxTypes not exactly same as xml values
      }
      return true;
   }
   
   //determine if two string arrays contain the same values (null entries are ignored)
   //equal arrays may be in different order and have different sizes (due to null entries)
   function isEqual(a1, a2)
   {
      var bEqual = ((a1!=null && a2!=null)?true:false);
      if (bEqual)
      {
         for (var i=0; i<a1.length; i++)
         {
            if (a1[i]!=null)
            {
               var bFound=false;
               for (var j=0; j<a2.length; j++)
               {
                  if (a2[j]!=null)
                  {
                     if (a1[i]==a2[j])
                     {
                        //found it
                        bFound=true;
                        break;   //j loop
                     }
                  }
               }
               if (!bFound)
               {
                  bEqual = false;
                  break;   //i loop
               }
            }
         }
      }
      return bEqual;
   }
   
   //----------------- methods to return criterion xml --------------------------
   function makeMargin(cnt,indent)
   {
      if (indent==null)
      {
         return "";
      }
      
      //must be printable
      var ret="";
      for (var i=0; i<cnt; i++)
      {
         ret+=indent;
      }
      return ret;
   }
   
   function buildCriterion(printable)
   {
      var ret="";
      //output the type and auxtype info
      var indent=null;
      var nl="";
      if (printable)
      {
         indent="   ";
         nl="\n";
      }
       
      //add start of criterion
      ret = "<"+XML_SELCRIT+">" + nl;
      
      if (criterion.types!=null && criterion.types.length>0)
      {
         //output type (ds class) name(s)
         ret += makeMargin(1,indent) + "<" + XML_TYPES + ">";
         for (var i=0; i<criterion.types.length; i++)
         {
            ret += makeMargin(2,indent) + "<" + XML_ID + ">";
            ret += xmlEncode(criterion.types[i]);
            ret += "</" + XML_ID + ">" + nl;
         }
         ret += "</" + XML_TYPES + ">" + nl;
         
         if (criterion.auxTypes!=null && criterion.auxTypes.length>0)
         {
            //output auxtype (ds auxclass) name(s)
            ret += makeMargin(1,indent) + "<"+XML_AUXTYPES+">";
            for (var i=0; i<criterion.auxTypes.length; i++)
            {
               ret += makeMargin(2,indent) + "<" + XML_ID + ">";
               ret += xmlEncode(criterion.auxTypes[i]);
               ret += "</" + XML_ID + ">" + nl;
            }
            ret += "</" + XML_AUXTYPES + ">" + nl;
         }
      }
       
      //output the group info, if exists
      if (isCriterionValid())
      {
         var grp;
         var row;
         //output each valid group
         for (var i=0; i<criterion.grps.length; i++)
         {
            if (isGrpValid(i))
            {
               //add start of group tag
               ret += makeMargin(1,indent) + "<"+XML_GROUP+">" + nl;
            
               //get the group
               grp=criterion.grps[i];
               
               //output valid rows in group
               for (var j=0; j<grp.rows.length; j++)
               {
                  if (!isRowEmpty(grp,j))
                  {
                     //we have a valid row of data
                     row = grp.rows[j];
                     
                     //add start of row
                     ret += makeMargin(2,indent) + "<"+XML_ROW+">" + nl;
                     
                     //add attribute
                     ret += makeMargin(3,indent) + "<"+XML_ATTRIB+">";
                     ret += xmlEncode(row.crit.attrib);
                     ret += "</"+XML_ATTRIB+">" + nl;
                     
                     //add operation
                     ret += makeMargin(3,indent) + "<"+XML_OP+">";
                     ret += xmlEncode(row.crit.op);
                     ret += "</"+XML_OP+">" + nl;
                     
                     //add attribute value
                     ret += makeMargin(3,indent) + "<"+XML_VALUE+">";
                     ret += xmlEncode(row.crit.attribVal);
                     ret += "</"+XML_VALUE+">" + nl;
                     
                     //add row logical op
                     ret += makeMargin(3,indent) + "<"+XML_ROWLOP+">";
                     ret += xmlEncode(row.rowLop);
                     ret += "</"+XML_ROWLOP+">" + nl;
                     
                     //add end of row
                     ret += makeMargin(2,indent) + "</"+XML_ROW+">" + nl;
                  }
               }//for rows
               
               //add group logical op
               ret += makeMargin(2,indent) + "<"+XML_GRPLOP+">";
               ret += xmlEncode(grp.grpLop);
               ret += "</"+XML_GRPLOP+">" + nl;
            
               //add end of group tag
               ret += makeMargin(1,indent) + "</"+XML_GROUP+">" + nl;
            }
         }//for groups
      }
      
      //add end of criterion
      ret+="</"+XML_SELCRIT+">"+nl;
      return ret;
   }