//formatUtils.js : this is used for cleaning up/formatting apr text for use in non-asi forms.


	function cleanPeriod(tmp){
		regX1=/\./g;   
		var newValue = tmp.replace(regX1, "!");
		return newValue;
	}
	
	function cleanQuotes(tmp){
		regX1=/&amp/g;
		regX2=/&quot;/g;
		
		return tmp.replace(regX1, "&").replace(regX2,"\"");
	}

	function removeExtraACPFields(maxNum){
     for(var k=0; k<maxNum; k++){
      var elems=document.getElementsByName("ACP~"+k);
      for(var j=0; j<elems.length; j++){
        if (elems[j].value.length==0){
          elems[j].name="x";
        }
      }
    }
  }
  
  //Checkedvals=array of values
  //checkboxes= array of checkbox objects
  function populateCBs(checkedVals, checkBoxes)
  {
    for(k=0; k<checkedVals.length; k++){
      for(j=0; j<checkBoxes.length; j++){
        if(checkedVals[k]==checkBoxes[j].value)
          checkBoxes[j].checked=true;
      }
    }
  }

  function populateRBs(value, radioButtons){
    for(k=0; k<radioButtons.length; k++){
      if(radioButtons[k].value==value){
        radioButtons[k].checked=true;
      }
    }
  }

  function populateSelect(selectedValue, selectElement){
    alert('populateSelect not tested yet. remove this alert when it has been verified');
    for(k=0; k<selectElement.options.length; k++)
    {
      if(selectElement.options[k].value==selectedValue){
        selectElement.options[k].selected=true;
      }
    }
  }

//arr= array to be stringified
//delim= string to put between elements
//start= prefix string
//finish= suffix string
//i.e. arrayToString({'alice','bob','carol'}, ';' , '[' , ']' )    ->  "[alice;bob;carol]"
  function arrayToString(arr, delim, start, finish)
  {
    var swap=start;
    for(var k=0; k<arr.length;k++)
    {
      if(k!=0)
        swap=swap.concat(delim);
      swap=swap.concat( arr[k]);
    }
    swap=swap.concat(finish);
    return swap;
  }

//str= string to be arrayified
//delim= string that sits between array entries
//trimFront= the number of characters to remove from the front of the string 
//trimBack= the number of characters to remove from the end of the string
//i.e. stringToArray("[alice;bob;carol]" , ";" , 1 , 1) -> (array)  {'alice','bob','carol'}
  function stringToArray(str, delim, trimFront, trimBack)
  {
    var arr=str.split(delim);
    if(arr.length==1 && arr[0].length==0)
    {
      arr=new Array(0);
    }
    else
    {
      arr[0]=arr[0].substr(trimFront);
      arr[arr.length-1]=arr[arr.length-1].substr(0,arr[arr.length-1].length-trimBack);
    }
    return arr;
  }

var maxLength=4000;

  function checkLength(field){
    if(field.value.length> maxLength){
      field.value=field.value.substring(0,maxLength);
    
    alert("You have exceeded the maximum length of "+maxLength+" for this field.");
    }
  }

  function disableSubmit(elem){
     var elems=elem.elements;
    for( var k=0; k< elems.length; k++){
      if(elems[k].type=="submit"){
        elems[k].disabled=true;
      }
    }
  }
var USERROLE_TRANSPROCESSOR=7;
var USERROLE_GSDPROCOFFICER=8;
var USERROLE_LEGALREPRESENTATIVE=9;
var USERROLE_OTHER=10;
var USERROLE_TEAMLEAD=1;

//var ACCESS_ALL=1;
//var ACCESS_READ=2;
//var ACCESS_ADDNOTES=3
		
//var accessOptions="<option value='"+ ACCESS_READ+"' selected>Read Only</option><option value='"+ACCESS_ADDNOTES+"'>Read Only + Add Notes</option>";
var otherRoleOptions="<option value='"+USERROLE_TRANSPROCESSOR +"'>Transaction Processor</option>" +
				"<option value='"+USERROLE_GSDPROCOFFICER +"'>GSDPR Representative</option>" +
                "<option value='"+USERROLE_LEGALREPRESENTATIVE +"'>Legal Representative</option>" +
                "<option value='"+USERROLE_OTHER +"' selected='true'>Other</option>";



//TODO: somehow get these constants out of resources...
function indivSet(val, fName, lName, pos, email, phone, roleDesc, roleID, theTable, updateArrays, form,doc)
{
  count = parseInt(doc.getElementById("counter").value);
  doc.getElementById("counter").value=count+1;

  var rowId;
  var myNewRow;
					
  //if this is going to the "other team members" section, need dropdowns for roles
  if(roleID== USERROLE_TRANSPROCESSOR ||
     roleID==USERROLE_GSDPROCOFFICER ||
     roleID==USERROLE_LEGALREPRESENTATIVE ||
     roleID==USERROLE_OTHER)
  {
    rowId = "teamRow"+count+"upi"+val;
	myNewRow = theTable.insertRow();
	myNewRow.className="FormBody";
    myNewRow.id = rowId;

    //the function called by the onChange will have to find roleID/upi in arrays, change roleId
    myNewRow.insertCell().innerHTML="<select class='TextBox' id='role"+count+"' onChange='changeRole("+val+", this.options[this.selectedIndex].value, \""+rowId+"\")'>"+otherRoleOptions+"</select>";
    //myNewRow.insertCell().innerHTML="<select class='FormBody' id='access"+count+"' onChange='changeAccess("+val+", this.options[this.selectedIndex].value)'>"+accessOptions+"</select>";
    roleSel=doc.getElementById("role"+count);
          
    for(var k=0; k<roleSel.options.length; k++)
    {
      if(roleSel.options[k].value==roleID)
        roleSel.selectedIndex=k;
    }
  } else if (roleID == USERROLE_TEAMLEAD){
	rowId= "teamRow" + count + "upi" + val;
   	myNewRow = theTable.insertRow();
	myNewRow.className="FormBody";
    myNewRow.id = rowId;
    z = doc.getElementById("teamLeadButton");
    z.style.visibility = "hidden";
    myNewRow.insertCell().innerHTML = "<span class='FormBody'>" + roleDesc + "</span>";
  } else {
	rowId = "upi" + val + "roleId" + roleID;
	for (var i = 0; i < theTable.rows.length ; i++)
	{
	  if (theTable.rows[i].id == rowId)
	  {
	    // ignore any role/upi we have already added
		return;
	  }
	}

	myNewRow = theTable.insertRow();
	myNewRow.className="FormBody";
    myNewRow.id = rowId;
    myNewRow.insertCell().innerHTML = "<span class='FormBody'>" + roleDesc + "</span>";
  }
  
  myNewRow.insertCell().innerHTML="<span class='FormBody'>" + val + "</span>";
  myNewRow.insertCell().innerHTML="<span class='FormBody'>" + fName + "</span>" + " " + "<span class='FormBody'>" + lName + "</span>";
  myNewRow.insertCell().innerHTML="<span class='FormBody'>" + pos + "</span>"; 
  myNewRow.insertCell().innerHTML="<span class='FormBody'>" + email + "</span>";
  var argString= "\'"+myNewRow.parentElement.parentElement.id+"\', "+roleID+",\'"+rowId+"\'";
  var removeLink="<a href=\"javascript:removeUser("+argString+")\"> remove </a>";
  myNewRow.insertCell().innerHTML = removeLink;

  //also update (append to) these lists
  if(updateArrays)
  {        
    doc.getElementById("roleList").value=doc.getElementById("roleList").value+","+roleID;
    doc.getElementById("upiList").value=doc.getElementById("upiList").value+',"'+val+'"';
    doc.getElementById("fNameList").value=doc.getElementById("fNameList").value+',"'+fName+'"';
    doc.getElementById("lNameList").value=doc.getElementById("lNameList").value+',"'+lName+'"';
    doc.getElementById("emailList").value=doc.getElementById("emailList").value+',"'+email+'"';
    doc.getElementById("phoneList").value=doc.getElementById("phoneList").value+',"'+phone+'"';
    doc.getElementById("posList").value=doc.getElementById("posList").value+',"'+pos+'"';
          
    if(doc.getElementById("upiList").value.charAt(0)==",")
      doc.getElementById("upiList").value=doc.getElementById("upiList").value.substr(1);
    if(doc.getElementById("fNameList").value.charAt(0)==",")
      doc.getElementById("fNameList").value=doc.getElementById("fNameList").value.substr(1);
    if(doc.getElementById("lNameList").value.charAt(0)==",")
      doc.getElementById("lNameList").value=doc.getElementById("lNameList").value.substr(1);
    if(doc.getElementById("emailList").value.charAt(0)==",")
      doc.getElementById("emailList").value=doc.getElementById("emailList").value.substr(1);
    if(doc.getElementById("phoneList").value.charAt(0)==",")
      doc.getElementById("phoneList").value=doc.getElementById("phoneList").value.substr(1);
    if(doc.getElementById("posList").value.charAt(0)==",")
      doc.getElementById("posList").value=doc.getElementById("posList").value.substr(1);
    if(doc.getElementById("roleList").value.charAt(0)==",")
      doc.getElementById("roleList").value=doc.getElementById("roleList").value.substr(1);

  }

  doc.getElementById("rowList").value=doc.getElementById("rowList").value+',"'+rowId+'"';
  if(doc.getElementById("rowList").value.charAt(0)==",")
    doc.getElementById("rowList").value=doc.getElementById("rowList").value.substr(1);

  count++;					
}

// ===================================================================
// Author: Matt Kruse <matt@mattkruse.com>
// WWW: http://www.mattkruse.com/
//
// NOTICE: You may use this code for any purpose, commercial or
// private, without any further permission from the author. You may
// remove this notice from your final code if you wish, however it is
// appreciated by the author if at least my web site address is kept.
//
// You may *NOT* re-distribute this code in any way except through its
// use. That means, you can include it in your product, or your web
// site, or any other form where the code is actually being used. You
// may not put the plain javascript up on your site for download or
// include it in your javascript libraries for download. 
// If you wish to share this code with others, please just point them
// to the URL instead.
// Please DO NOT link directly to my .js files from your site. Copy
// the files to your server and use them there. Thank you.
// ===================================================================

// HISTORY
// ------------------------------------------------------------------
// March 14, 2003: Added ability to disable individual dates or date
//      ranges, display as light gray and strike-through
// March 14, 2003: Removed dependency on graypixel.gif and instead 
///     use table border coloring
// March 12, 2003: Modified showCalendar() function to allow optional
//      start-date parameter
// March 11, 2003: Modified select() function to allow optional
//      start-date parameter
/* 
DESCRIPTION: This object implements a popup calendar to allow the user to
select a date, month, quarter, or year.

COMPATABILITY: Works with Netscape 4.x, 6.x, IE 5.x on Windows. Some small
positioning errors - usually with Window positioning - occur on the 
Macintosh platform.
The calendar can be modified to work for any location in the world by 
changing which weekday is displayed as the first column, changing the month
names, and changing the column headers for each day.

USAGE:
// Create a new CalendarPopup object of type WINDOW
var cal = new CalendarPopup(); 

// Create a new CalendarPopup object of type DIV using the DIV named 'mydiv'
var cal = new CalendarPopup('mydiv'); 

// Easy method to link the popup calendar with an input box. 
cal.select(inputObject, anchorname, dateFormat);
// Same method, but passing a default date other than the field's current value
cal.select(inputObject, anchorname, dateFormat, '01/02/2000');
// This is an example call to the popup calendar from a link to populate an 
// input box. Note that to use this, date.js must also be included!!
<A HREF="#" onClick="cal.select(document.forms[0].date,'anchorname','MM/dd/yyyy'); return false;">Select</A>

// Set the type of date select to be used. By default it is 'date'.
cal.setDisplayType(type);

// When a date, month, quarter, or year is clicked, a function is called and
// passed the details. You must write this function, and tell the calendar
// popup what the function name is.
// Function to be called for 'date' select receives y, m, d
cal.setReturnFunction(functionname);
// Function to be called for 'month' select receives y, m
cal.setReturnMonthFunction(functionname);
// Function to be called for 'quarter' select receives y, q
cal.setReturnQuarterFunction(functionname);
// Function to be called for 'year' select receives y
cal.setReturnYearFunction(functionname);

// Show the calendar relative to a given anchor
cal.showCalendar(anchorname);

// Hide the calendar. The calendar is set to autoHide automatically
cal.hideCalendar();

// Set the month names to be used. Default are English month names
cal.setMonthNames("January","February","March",...);

// Set the month abbreviations to be used. Default are English month abbreviations
cal.setMonthAbbreviations("Jan","Feb","Mar",...);

// Set the text to be used above each day column. The days start with 
// sunday regardless of the value of WeekStartDay
cal.setDayHeaders("S","M","T",...);

// Set the day for the first column in the calendar grid. By default this
// is Sunday (0) but it may be changed to fit the conventions of other
// countries.
cal.setWeekStartDay(1); // week is Monday - Saturday

// Set the weekdays which should be disabled in the 'date' select popup. You can
// then allow someone to only select week end dates, or Tuedays, for example
cal.setDisabledWeekDays(0,1); // To disable selecting the 1st or 2nd days of the week

// Selectively disable individual days or date ranges. Disabled days will not
// be clickable, and show as strike-through text on current browsers.
// Date format is any format recognized by parseDate() in date.js
// Pass a single date to disable:
cal.addDisabledDates("2003-01-01");
// Pass null as the first parameter to mean "anything up to and including" the
// passed date:
cal.addDisabledDates(null, "01/02/03");
// Pass null as the second parameter to mean "including the passed date and
// anything after it:
cal.addDisabledDates("Jan 01, 2003", null);
// Pass two dates to disable all dates inbetween and including the two
cal.addDisabledDates("January 01, 2003", "Dec 31, 2003");

// When the 'year' select is displayed, set the number of years back from the 
// current year to start listing years. Default is 2.
cal.setYearSelectStartOffset(2);

// Text for the word "Today" appearing on the calendar
cal.setTodayText("Today");

// Set the calendar offset to be different than the default. By default it
// will appear just below and to the right of the anchorname. So if you have
// a text box where the date will go and and anchor immediately after the
// text box, the calendar will display immediately under the text box.
cal.offsetX = 20;
cal.offsetY = 20;

NOTES:
1) Requires the functions in AnchorPosition.js and PopupWindow.js

2) Your anchor tag MUST contain both NAME and ID attributes which are the 
   same. For example:
   <A NAME="test" ID="test"> </A>

3) There must be at least a space between <A> </A> for IE5.5 to see the 
   anchor tag correctly. Do not do <A></A> with no space.

4) When a CalendarPopup object is created, a handler for 'onmouseup' is
   attached to any event handler you may have already defined. Do NOT define
   an event handler for 'onmouseup' after you define a CalendarPopup object 
   or the autoHide() will not work correctly.
   
5) The calendar popup display uses style sheets to make it look nice.

*/ 

// CONSTRUCTOR for the CalendarPopup Object
function CalendarPopup() {
	var c;
	if (arguments.length>0) {
		c = new PopupWindow(arguments[0]);
		}
	else {
		c = new PopupWindow();
		c.setSize(150,175);
		}
	c.offsetX = 0;//-152;
	c.offsetY = 0;//25;
	c.autoHide();
	// Calendar-specific properties
	c.monthNames = new Array("January","February","March","April","May","June","July","August","September","October","November","December");
	c.monthAbbreviations = new Array("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec");
	c.dayHeaders = new Array("S","M","T","W","T","F","S");
	c.returnFunction = "CalendarPopup_tmpReturnFunction";
	c.returnMonthFunction = "CalendarPopup_tmpReturnMonthFunction";
	c.returnQuarterFunction = "CalendarPopup_tmpReturnQuarterFunction";
	c.returnYearFunction = "CalendarPopup_tmpReturnYearFunction";
	c.weekStartDay = 0;
	c.isShowYearNavigation = false;
	c.displayType = "date";
	c.disabledWeekDays = new Object();
	c.disabledDatesExpression = "";
	c.yearSelectStartOffset = 2;
	c.currentDate = null;
	c.todayText="Today";
	window.CalendarPopup_targetInput = null;
	window.CalendarPopup_dateFormat = "mm/dd/yyyy";
	// Method mappings
	c.setReturnFunction = CalendarPopup_setReturnFunction;
	c.setReturnMonthFunction = CalendarPopup_setReturnMonthFunction;
	c.setReturnQuarterFunction = CalendarPopup_setReturnQuarterFunction;
	c.setReturnYearFunction = CalendarPopup_setReturnYearFunction;
	c.setMonthNames = CalendarPopup_setMonthNames;
	c.setMonthAbbreviations = CalendarPopup_setMonthAbbreviations;
	c.setDayHeaders = CalendarPopup_setDayHeaders;
	c.setWeekStartDay = CalendarPopup_setWeekStartDay;
	c.setDisplayType = CalendarPopup_setDisplayType;
	c.setDisabledWeekDays = CalendarPopup_setDisabledWeekDays;
	c.addDisabledDates = CalendarPopup_addDisabledDates;
	c.setYearSelectStartOffset = CalendarPopup_setYearSelectStartOffset;
	c.setTodayText = CalendarPopup_setTodayText;
	c.showYearNavigation = CalendarPopup_showYearNavigation;
	c.showCalendar = CalendarPopup_showCalendar;
	c.hideCalendar = CalendarPopup_hideCalendar;
	c.getStyles = CalendarPopup_getStyles;
	c.refreshCalendar = CalendarPopup_refreshCalendar;
	c.getCalendar = CalendarPopup_getCalendar;
	c.select = CalendarPopup_select;
	// Return the object
	return c;
	}

// Temporary default functions to be called when items clicked, so no error is thrown
function CalendarPopup_tmpReturnFunction(y,m,d) { 
	if (window.CalendarPopup_targetInput!=null) {
		var dt = new Date(y,m-1,d,0,0,0);
		window.CalendarPopup_targetInput.value = formatDate(dt,window.CalendarPopup_dateFormat);
		}
	else {
		alert('Use setReturnFunction() to define which function will get the clicked results!'); 
		}
	}
function CalendarPopup_tmpReturnMonthFunction(y,m) { 
	alert('Use setReturnMonthFunction() to define which function will get the clicked results!\nYou clicked: year='+y+' , month='+m); 
	}
function CalendarPopup_tmpReturnQuarterFunction(y,q) { 
	alert('Use setReturnQuarterFunction() to define which function will get the clicked results!\nYou clicked: year='+y+' , quarter='+q); 
	}
function CalendarPopup_tmpReturnYearFunction(y) { 
	alert('Use setReturnYearFunction() to define which function will get the clicked results!\nYou clicked: year='+y); 
	}

// Set the name of the functions to call to get the clicked item
function CalendarPopup_setReturnFunction(name) { this.returnFunction = name; }
function CalendarPopup_setReturnMonthFunction(name) { this.returnMonthFunction = name; }
function CalendarPopup_setReturnQuarterFunction(name) { this.returnQuarterFunction = name; }
function CalendarPopup_setReturnYearFunction(name) { this.returnYearFunction = name; }

// Over-ride the built-in month names
function CalendarPopup_setMonthNames() {
	for (var i=0; i<arguments.length; i++) { this.monthNames[i] = arguments[i]; }
	}

// Over-ride the built-in month abbreviations
function CalendarPopup_setMonthAbbreviations() {
	for (var i=0; i<arguments.length; i++) { this.monthAbbreviations[i] = arguments[i]; }
	}

// Over-ride the built-in column headers for each day
function CalendarPopup_setDayHeaders() {
	for (var i=0; i<arguments.length; i++) { this.dayHeaders[i] = arguments[i]; }
	}

// Set the day of the week (0-7) that the calendar display starts on
// This is for countries other than the US whose calendar displays start on Monday(1), for example
function CalendarPopup_setWeekStartDay(day) { this.weekStartDay = day; }

// Show next/last year navigation links
function CalendarPopup_showYearNavigation() { this.isShowYearNavigation = true; }

// Which type of calendar to display
function CalendarPopup_setDisplayType(type) {
	if (type!="date"&&type!="week-end"&&type!="month"&&type!="quarter"&&type!="year") { alert("Invalid display type! Must be one of: date,week-end,month,quarter,year"); return false; }
	this.displayType=type;
	}

// How many years back to start by default for year display
function CalendarPopup_setYearSelectStartOffset(num) { this.yearSelectStartOffset=num; }

// Set which weekdays should not be clickable
function CalendarPopup_setDisabledWeekDays() {
	this.disabledWeekDays = new Object();
	for (var i=0; i<arguments.length; i++) { this.disabledWeekDays[arguments[i]] = true; }
	}
	
// Disable individual dates or ranges
// Builds an internal logical test which is run via eval() for efficiency
function CalendarPopup_addDisabledDates(start, end) {
	if (arguments.length==1) { end=start; }
	if (start==null && end==null) { return; }
	if (this.disabledDatesExpression!="") { this.disabledDatesExpression+= "||"; }
	if (start!=null) { start = parseDate(start); start=""+start.getFullYear()+LZ(start.getMonth()+1)+LZ(start.getDate());}
	if (end!=null) { end=parseDate(end); end=""+end.getFullYear()+LZ(end.getMonth()+1)+LZ(end.getDate());}
	if (start==null) { this.disabledDatesExpression+="(ds<="+end+")"; }
	else if (end  ==null) { this.disabledDatesExpression+="(ds>="+start+")"; }
	else { this.disabledDatesExpression+="(ds>="+start+"&&ds<="+end+")"; }
	}
	
// Set the text to use for the "Today" link
function CalendarPopup_setTodayText(text) {
	this.todayText = text;
	}

// Hide a calendar object
function CalendarPopup_hideCalendar() {
	if (arguments.length > 0) { window.popupWindowObjects[arguments[0]].hidePopup(); }
	else { this.hidePopup(); }
	}

// Refresh the contents of the calendar display
function CalendarPopup_refreshCalendar(index) {
	var calObject = window.popupWindowObjects[index];
	if (arguments.length>1) { 
		calObject.populate(calObject.getCalendar(arguments[1],arguments[2],arguments[3],arguments[4],arguments[5]));
		}
	else {
		calObject.populate(calObject.getCalendar());
		}
	calObject.refresh();
	}

// Populate the calendar and display it
function CalendarPopup_showCalendar(anchorname) {
	if (arguments.length>1) {
		if (arguments[1]==null||arguments[1]=="") {
			this.currentDate=new Date();
			}
		else {
			this.currentDate=new Date(parseDate(arguments[1]));
			}
		}
	this.populate(this.getCalendar());
	this.showPopup(anchorname);
	}

// Simple method to interface popup calendar with a text-entry box
function CalendarPopup_select(inputobj, linkname, format) {
  
	var selectedDate=(arguments.length>3)?arguments[3]:null;
	if (!window.getDateFromFormat) {
		alert("calendar.select: To use this method you must also include 'date.js' for date formatting");
		return;
		}
	if (this.displayType!="date"&&this.displayType!="week-end") {
		alert("calendar.select: This function can only be used with displayType 'date' or 'week-end'");
		return;
		}
	if (inputobj.type!="text" && inputobj.type!="hidden" && inputobj.type!="textarea") { 
		alert("calendar.select: Input object passed is not a valid form input object"); 
		window.CalendarPopup_targetInput=null;
		return;
		}
	window.CalendarPopup_targetInput = inputobj;
	this.currentDate=null;
	var time=0;
	if (selectedDate!=null) {
		time = getDateFromFormat(selectedDate,format)
		}
	else if (inputobj.value!="") {
		time = getDateFromFormat(inputobj.value,format);
		}
	if (selectedDate!=null || inputobj.value!="") {
		if (time==0) { this.currentDate=null; }
		else { this.currentDate=new Date(time); }
		}
	window.CalendarPopup_dateFormat = format;
	this.showCalendar(linkname);
	}
	
// Get style block needed to display the calendar correctly
function CalendarPopup_getStyles() {
	var result = "";
	result += "<STYLE>\n";
	result += "TD.cal,TD.calday,TD.calmonth,TD.caltoday,A.textlink,.disabledtextlink { font-family:arial; font-size: 8pt; }\n";
	result += "TD.calday{border:solid thin #C0C0C0;border-width:0 0 1 0;}\n";
	result += "TD.calmonth{text-align:right;}\n";
	result += "TD.caltoday{text-align:right;color:white;background-color:#C0C0C0;border-width:1;border:solid thing #800000;}\n";
	result += "TD.textlink{border:solid thin #C0C0C0; border-width:1 0 0 0;}\n";
	result += "A.textlink{height:20px;color:black;}\n";
	result += ".disabledtextlink{height:20px;color:#808080;}\n";
	result += "A.cal{text-decoration:none;color:#000000;}\n";
	result += "A.calthismonth{text-decoration:none;color:#000000;}\n";
	result += "A.calothermonth{text-decoration:none; color:#808080;}\n";
	result += ".calnotclickable{color:#808080;}\n";
	result += ".disabled{color:#D0D0D0;text-decoration:line-through;}\n";
	result += "</STYLE>\n";
	return result;
	}

// Return a string containing all the calendar code to be displayed
function CalendarPopup_getCalendar() {
	var now = new Date();
	// Reference to window
	if (this.type == "WINDOW") { var windowref = "window.opener."; }
	else { var windowref = ""; }
	var result = "";
	// If POPUP, write entire HTML document
	if (this.type == "WINDOW") {
		result += "<HTML><HEAD><TITLE>Calendar</TITLE>"+this.getStyles()+"</HEAD><BODY MARGINWIDTH=0 MARGINHEIGHT=0 TOPMARGIN=0 RIGHTMARGIN=0 LEFTMARGIN=0>\n";
		result += '<CENTER><TABLE WIDTH=100% BORDER=0 BORDERWIDTH=0 CELLSPACING=0 CELLPADDING=0>\n';
		}
	else {
		result += '<TABLE WIDTH=144 BORDER=1 BORDERWIDTH=1 BORDERCOLOR="#808080" CELLSPACING=0 CELLPADDING=1>\n';
		result += '<TR><TD ALIGN=CENTER>\n';
		result += '<CENTER>\n';
		}
	var t144="<TABLE WIDTH=144 BORDER=0 BORDERWIDTH=0 CELLSPACING=0 CELLPADDING=0>";
	// Code for DATE display (default)
	// -------------------------------
	if (this.displayType=="date" || this.displayType=="week-end") {
		if (this.currentDate==null) { this.currentDate = now; }
		if (arguments.length > 0) { var month = arguments[0]; }
			else { var month = this.currentDate.getMonth()+1; }
		if (arguments.length > 1) { var year = arguments[1]; }
			else { var year = this.currentDate.getFullYear(); }
		var daysinmonth= new Array(0,31,28,31,30,31,30,31,31,30,31,30,31);
		if ( ( (year%4 == 0)&&(year%100 != 0) ) || (year%400 == 0) ) {
			daysinmonth[2] = 29;
			}
		var current_month = new Date(year,month-1,1);
		var display_year = year;
		var display_month = month;
		var display_date = 1;
		var weekday= current_month.getDay();
		var offset = 0;
		if (weekday >= this.weekStartDay) {
			offset = weekday - this.weekStartDay;
			}
		else {
			offset = 7-this.weekStartDay+weekday;
			}
		if (offset > 0) {
			display_month--;
			if (display_month < 1) { display_month = 12; display_year--; }
			display_date = daysinmonth[display_month]-offset+1;
			}
		var next_month = month+1;
		var next_month_year = year;
		if (next_month > 12) { next_month=1; next_month_year++; }
		var last_month = month-1;
		var last_month_year = year;
		if (last_month < 1) { last_month=12; last_month_year--; }
		var date_class;
		if (this.type!="WINDOW") {
			result += t144;
			}
		result += '<TR BGCOLOR="#C0C0C0">\n';
		var refresh = 'javascript:'+windowref+'CalendarPopup_refreshCalendar';
		var td = '<TD BGCOLOR="#C0C0C0" CLASS="cal" ALIGN=CENTER VALIGN=MIDDLE WIDTH=';
		if (this.isShowYearNavigation) {
			result += td+'10><B><A CLASS="cal" HREF="'+refresh+'('+this.index+','+last_month+','+last_month_year+');">&lt;</A></B></TD>';
			result += td+'58>'+this.monthNames[month-1]+'</TD>';
			result += td+'10><B><A CLASS="cal" HREF="'+refresh+'('+this.index+','+next_month+','+next_month_year+');">&gt;</A></B></TD>';
			result += td+'10>&nbsp;</TD>';
			result += td+'10><B><A CLASS="cal" HREF="'+refresh+'('+this.index+','+month+','+(year-1)+');">&lt;</A></B></TD>';
			result += td+'36>'+year+'</TD>';
			result += td+'10><B><A CLASS="cal" HREF="'+refresh+'('+this.index+','+month+','+(year+1)+');">&gt;</A></B></TD>';
			}
		else {
			result += td+'22><B><A CLASS="cal" HREF="'+refresh+'('+this.index+','+last_month+','+last_month_year+');">&lt;&lt;</A></B></TD>\n';
			result += td+'100>'+this.monthNames[month-1]+' '+year+'</TD>\n';
			result += td+'22><B><A CLASS="cal" HREF="'+refresh+'('+this.index+','+next_month+','+next_month_year+');">&gt;&gt;</A></B></TD>\n';
			}
		result += '</TR></TABLE>\n';
		result += '<TABLE WIDTH=120 BORDER=0 CELLSPACING=0 CELLPADDING=1 ALIGN=CENTER>\n';
		result += '<TR>\n';
		var td = '	<TD CLASS="calday" ALIGN=RIGHT WIDTH=14%>';
		for (var j=0; j<7; j++) {
			result += td+this.dayHeaders[(this.weekStartDay+j)%7]+'</TD>\n';
			}
		result += '</TR>\n';
		for (var row=1; row<=6; row++) {
			result += '<TR>\n';
			for (var col=1; col<=7; col++) {
				if (display_month == month) {
					date_class = "calthismonth";
					}
				else {
					date_class = "calothermonth";
					}
				if ((display_month == this.currentDate.getMonth()+1) && (display_date==this.currentDate.getDate()) && (display_year==this.currentDate.getFullYear())) {
					td_class="caltoday";
					}
				else {
					td_class="calmonth";
					}
				var disabled=false;
				if (this.disabledDatesExpression!="") {
					var ds=""+display_year+LZ(display_month)+LZ(display_date);
					eval("disabled=("+this.disabledDatesExpression+")");
					}
				if (disabled || this.disabledWeekDays[col-1]) {
					date_class=(disabled)?"disabled":"calnotclickable";
					result += '	<TD CLASS="'+td_class+'"><SPAN CLASS="'+date_class+'">'+display_date+'</SPAN></TD>\n';
					}
				else {
					var selected_date = display_date;
					var selected_month = display_month;
					var selected_year = display_year;
					if (this.displayType=="week-end") {
						var d = new Date(selected_year,selected_month-1,selected_date,0,0,0,0);
						d.setDate(d.getDate() + (7-col));
						selected_year = d.getYear();
						if (selected_year < 1000) { selected_year += 1900; }
						selected_month = d.getMonth()+1;
						selected_date = d.getDate();
						}
					result += '	<TD CLASS="'+td_class+'"><A HREF="javascript:'+windowref+this.returnFunction+'('+selected_year+','+selected_month+','+selected_date+');'+windowref+'CalendarPopup_hideCalendar(\''+this.index+'\');" CLASS="'+date_class+'">'+display_date+'</A></TD>\n';
					}
				display_date++;
				if (display_date > daysinmonth[display_month]) {
					display_date=1;
					display_month++;
					}
				if (display_month > 12) {
					display_month=1;
					display_year++;
					}
				}
			result += '</TR>';
			}
		var current_weekday = now.getDay();
		result += '<TR>\n';
		result += '	<TD COLSPAN=7 ALIGN=CENTER CLASS="textlink">\n';
		if (this.disabledWeekDays[current_weekday+1]) {
			result += '		<SPAN CLASS="disabledtextlink">'+this.todayText+'</SPAN>\n';
			}
		else {
			result += '		<A CLASS="textlink" HREF="javascript:'+windowref+this.returnFunction+'(\''+now.getFullYear()+'\',\''+(now.getMonth()+1)+'\',\''+now.getDate()+'\');'+windowref+'CalendarPopup_hideCalendar(\''+this.index+'\');">'+this.todayText+'</A>\n';
			}
		result += '		<BR>\n';
		result += '	</TD></TR></TABLE></CENTER></TD></TR></TABLE>\n';
	}

	// Code common for MONTH, QUARTER, YEAR
	// ------------------------------------
	if (this.displayType=="month" || this.displayType=="quarter" || this.displayType=="year") {
		if (arguments.length > 0) { var year = arguments[0]; }
		else { 
			if (this.displayType=="year") {	var year = now.getFullYear()-this.yearSelectStartOffset; }
			else { var year = now.getFullYear(); }
			}
		if (this.displayType!="year" && this.isShowYearNavigation) {
			result += t144;
			result += '<TR BGCOLOR="#C0C0C0">\n';
			result += '	<TD BGCOLOR="#C0C0C0" CLASS="cal" WIDTH=22 ALIGN=CENTER VALIGN=MIDDLE><B><A CLASS="cal" HREF="javascript:'+windowref+'CalendarPopup_refreshCalendar('+this.index+','+(year-1)+');">&lt;&lt;</A></B></TD>\n';
			result += '	<TD BGCOLOR="#C0C0C0" CLASS="cal" WIDTH=100 ALIGN=CENTER>'+year+'</TD>\n';
			result += '	<TD BGCOLOR="#C0C0C0" CLASS="cal" WIDTH=22 ALIGN=CENTER VALIGN=MIDDLE><B><A CLASS="cal" HREF="javascript:'+windowref+'CalendarPopup_refreshCalendar('+this.index+','+(year+1)+');">&gt;&gt;</A></B></TD>\n';
			result += '</TR></TABLE>\n';
			}
		}
		
	// Code for MONTH display 
	// ----------------------
	if (this.displayType=="month") {
		// If POPUP, write entire HTML document
		result += '<TABLE WIDTH=120 BORDER=0 CELLSPACING=1 CELLPADDING=0 ALIGN=CENTER>\n';
		for (var i=0; i<4; i++) {
			result += '<TR>';
			for (var j=0; j<3; j++) {
				var monthindex = ((i*3)+j);
				result += '<TD WIDTH=33% ALIGN=CENTER><A CLASS="textlink" HREF="javascript:'+windowref+this.returnMonthFunction+'('+year+','+(monthindex+1)+');'+windowref+'CalendarPopup_hideCalendar(\''+this.index+'\');" CLASS="'+date_class+'">'+this.monthAbbreviations[monthindex]+'</A></TD>';
				}
			result += '</TR>';
			}
		result += '</TABLE></CENTER></TD></TR></TABLE>\n';
		}
	
	// Code for QUARTER display
	// ------------------------
	if (this.displayType=="quarter") {
		result += '<BR><TABLE WIDTH=120 BORDER=1 CELLSPACING=0 CELLPADDING=0 ALIGN=CENTER>\n';
		for (var i=0; i<2; i++) {
			result += '<TR>';
			for (var j=0; j<2; j++) {
				var quarter = ((i*2)+j+1);
				result += '<TD WIDTH=50% ALIGN=CENTER><BR><A CLASS="textlink" HREF="javascript:'+windowref+this.returnQuarterFunction+'('+year+','+quarter+');'+windowref+'CalendarPopup_hideCalendar(\''+this.index+'\');" CLASS="'+date_class+'">Q'+quarter+'</A><BR><BR></TD>';
				}
			result += '</TR>';
			}
		result += '</TABLE></CENTER></TD></TR></TABLE>\n';
		}

	// Code for YEAR display
	// ---------------------
	if (this.displayType=="year") {
		var yearColumnSize = 4;
		result += t144;
		result += '<TR BGCOLOR="#C0C0C0">\n';
		result += '	<TD BGCOLOR="#C0C0C0" CLASS="cal" WIDTH=50% ALIGN=CENTER VALIGN=MIDDLE><B><A CLASS="cal" HREF="javascript:'+windowref+'CalendarPopup_refreshCalendar('+this.index+','+(year-(yearColumnSize*2))+');">&lt;&lt;</A></B></TD>\n';
		result += '	<TD BGCOLOR="#C0C0C0" CLASS="cal" WIDTH=50% ALIGN=CENTER VALIGN=MIDDLE><B><A CLASS="cal" HREF="javascript:'+windowref+'CalendarPopup_refreshCalendar('+this.index+','+(year+(yearColumnSize*2))+');">&gt;&gt;</A></B></TD>\n';
		result += '</TR></TABLE>\n';
		result += '<TABLE WIDTH=120 BORDER=0 CELLSPACING=1 CELLPADDING=0 ALIGN=CENTER>\n';
		for (var i=0; i<yearColumnSize; i++) {
			for (var j=0; j<2; j++) {
				var currentyear = year+(j*yearColumnSize)+i;
				result += '<TD WIDTH=50% ALIGN=CENTER><A CLASS="textlink" HREF="javascript:'+windowref+this.returnYearFunction+'('+currentyear+');'+windowref+'CalendarPopup_hideCalendar(\''+this.index+'\');" CLASS="'+date_class+'">'+currentyear+'</A></TD>';
				}
			result += '</TR>';
			}
		result += '</TABLE></CENTER></TD></TR></TABLE>\n';
		}
	// Common
	if (this.type == "WINDOW") {
		result += "</BODY></HTML>\n";
		}
	return result;
	}

// ===================================================================
// Author: Matt Kruse <matt@mattkruse.com>
// WWW: http://www.mattkruse.com/
//
// NOTICE: You may use this code for any purpose, commercial or
// private, without any further permission from the author. You may
// remove this notice from your final code if you wish, however it is
// appreciated by the author if at least my web site address is kept.
//
// You may *NOT* re-distribute this code in any way except through its
// use. That means, you can include it in your product, or your web
// site, or any other form where the code is actually being used. You
// may not put the plain javascript up on your site for download or
// include it in your javascript libraries for download. 
// If you wish to share this code with others, please just point them
// to the URL instead.
// Please DO NOT link directly to my .js files from your site. Copy
// the files to your server and use them there. Thank you.
// ===================================================================

/* 
PopupWindow.js
Author: Matt Kruse
Last modified: 05/15/03

DESCRIPTION: This object allows you to easily and quickly popup a window
in a certain place. The window can either be a DIV or a separate browser
window.

COMPATABILITY: Works with Netscape 4.x, 6.x, IE 5.x on Windows. Some small
positioning errors - usually with Window positioning - occur on the 
Macintosh platform. Due to bugs in Netscape 4.x, populating the popup 
window with <STYLE> tags may cause errors.

USAGE:
// Create an object for a WINDOW popup
var win = new PopupWindow(); 

// Create an object for a DIV window using the DIV named 'mydiv'
var win = new PopupWindow('mydiv'); 

// Set the window to automatically hide itself when the user clicks 
// anywhere else on the page except the popup
win.autoHide(); 

// Show the window relative to the anchor name passed in
win.showPopup(anchorname);

// Hide the popup
win.hidePopup();

// Set the size of the popup window (only applies to WINDOW popups
win.setSize(width,height);

// Populate the contents of the popup window that will be shown. If you 
// change the contents while it is displayed, you will need to refresh()
win.populate(string);

// set the URL of the window, rather than populating its contents
// manually
win.setUrl("http://www.site.com/");

// Refresh the contents of the popup
win.refresh();

// Specify how many pixels to the right of the anchor the popup will appear
win.offsetX = 50;

// Specify how many pixels below the anchor the popup will appear
win.offsetY = 100;

NOTES:
1) Requires the functions in AnchorPosition.js

2) Your anchor tag MUST contain both NAME and ID attributes which are the 
   same. For example:
   <A NAME="test" ID="test"> </A>

3) There must be at least a space between <A> </A> for IE5.5 to see the 
   anchor tag correctly. Do not do <A></A> with no space.

4) When a PopupWindow object is created, a handler for 'onmouseup' is
   attached to any event handler you may have already defined. Do NOT define
   an event handler for 'onmouseup' after you define a PopupWindow object or
   the autoHide() will not work correctly.
*/ 

// Set the position of the popup window based on the anchor
function PopupWindow_getXYPosition(anchorname) {
	var coordinates;
	if (this.type == "WINDOW") {
		coordinates = getAnchorWindowPosition(anchorname);
		}
	else {
		coordinates = getAnchorPosition(anchorname);
		}
	this.x = coordinates.x;
	this.y = coordinates.y;
	}
// Set width/height of DIV/popup window
function PopupWindow_setSize(width,height) {
	this.width = width;
	this.height = height;
	}
// Fill the window with contents
function PopupWindow_populate(contents) {
	this.contents = contents;
	this.populated = false;
	}
// Set the URL to go to
function PopupWindow_setUrl(url) {
	this.url = url;
	}
// Set the window popup properties
function PopupWindow_setWindowProperties(props) {
	this.windowProperties = props;
	}
// Refresh the displayed contents of the popup
function PopupWindow_refresh() {
	if (this.divName != null) {
		// refresh the DIV object
		if (this.use_gebi) {
			document.getElementById(this.divName).innerHTML = this.contents;
			}
		else if (this.use_css) { 
			document.all[this.divName].innerHTML = this.contents;
			}
		else if (this.use_layers) { 
			var d = document.layers[this.divName]; 
			d.document.open();
			d.document.writeln(this.contents);
			d.document.close();
			}
		}
	else {
		if (this.popupWindow != null && !this.popupWindow.closed) {
			if (this.url!="") {
				this.popupWindow.location.href=this.url;
				}
			else {
				this.popupWindow.document.open();
				this.popupWindow.document.writeln(this.contents);
				this.popupWindow.document.close();
			}
			this.popupWindow.focus();
			}
		}
	}
// Position and show the popup, relative to an anchor object
function PopupWindow_showPopup(anchorname) {
  
	this.getXYPosition(anchorname);
	this.x += this.offsetX;
	this.y += this.offsetY;
	if (!this.populated && (this.contents != "")) {
		this.populated = true;
		this.refresh();
		}
	if (this.divName != null) {
		// Show the DIV object
		if (this.use_gebi) {
			document.getElementById(this.divName).style.left = this.x;
			document.getElementById(this.divName).style.top = this.y;
			document.getElementById(this.divName).style.visibility = "visible";
			}
		else if (this.use_css) {
			document.all[this.divName].style.left = this.x;
			document.all[this.divName].style.top = this.y;
			document.all[this.divName].style.visibility = "visible";
			}
		else if (this.use_layers) {
			document.layers[this.divName].left = this.x;
			document.layers[this.divName].top = this.y;
			document.layers[this.divName].visibility = "visible";
			}
		}
	else {
		if (this.popupWindow == null || this.popupWindow.closed) {
			// If the popup window will go off-screen, move it so it doesn't
			if (this.x<0) { this.x=0; }
			if (this.y<0) { this.y=0; }
			if (screen && screen.availHeight) {
				if ((this.y + this.height) > screen.availHeight) {
					this.y = screen.availHeight - this.height;
					}
				}
			if (screen && screen.availWidth) {
				if ((this.x + this.width) > screen.availWidth) {
					this.x = screen.availWidth - this.width;
					}
				}
			this.popupWindow = window.open("about:blank","window_"+anchorname,this.windowProperties+",width="+this.width+",height="+this.height+",screenX="+this.x+",left="+this.x+",screenY="+this.y+",top="+this.y+"");
			}
		this.refresh();
		}
	}
// Hide the popup
function PopupWindow_hidePopup() {
	if (this.divName != null) {
		if (this.use_gebi) {
			document.getElementById(this.divName).style.visibility = "hidden";
			}
		else if (this.use_css) {
			document.all[this.divName].style.visibility = "hidden";
			}
		else if (this.use_layers) {
			document.layers[this.divName].visibility = "hidden";
			}
		}
	else {
		if (this.popupWindow && !this.popupWindow.closed) {
			this.popupWindow.close();
			this.popupWindow = null;
			}
		}
	}
// Pass an event and return whether or not it was the popup DIV that was clicked
function PopupWindow_isClicked(e) {
	if (this.divName != null) {
		if (this.use_layers) {
			var clickX = e.pageX;
			var clickY = e.pageY;
			var t = document.layers[this.divName];
			if ((clickX > t.left) && (clickX < t.left+t.clip.width) && (clickY > t.top) && (clickY < t.top+t.clip.height)) {
				return true;
				}
			else { return false; }
			}
		else if (document.all) { // Need to hard-code this to trap IE for error-handling
			var t = window.event.srcElement;
			while (t.parentElement != null) {
				if (t.id==this.divName) {
					return true;
					}
				t = t.parentElement;
				}
			return false;
			}
		else if (this.use_gebi) {
			var t = e.originalTarget;
			while (t.parentNode != null) {
				if (t.id==this.divName) {
					return true;
					}
				t = t.parentNode;
				}
			return false;
			}
		return false;
		}
	return false;
	}

// Check an onMouseDown event to see if we should hide
function PopupWindow_hideIfNotClicked(e) {
	if (this.autoHideEnabled && !this.isClicked(e)) {
		this.hidePopup();
		}
	}
// Call this to make the DIV disable automatically when mouse is clicked outside it
function PopupWindow_autoHide() {
	this.autoHideEnabled = true;
	}
// This global function checks all PopupWindow objects onmouseup to see if they should be hidden
function PopupWindow_hidePopupWindows(e) {
	for (var i=0; i<popupWindowObjects.length; i++) {
		if (popupWindowObjects[i] != null) {
			var p = popupWindowObjects[i];
			p.hideIfNotClicked(e);
			}
		}
	}
// Run this immediately to attach the event listener
function PopupWindow_attachListener() {
	if (document.layers) {
		document.captureEvents(Event.MOUSEUP);
		}
	window.popupWindowOldEventListener = document.onmouseup;
	if (window.popupWindowOldEventListener != null) {
		document.onmouseup = new Function("window.popupWindowOldEventListener(); PopupWindow_hidePopupWindows();");
		}
	else {
		document.onmouseup = PopupWindow_hidePopupWindows;
		}
	}
// CONSTRUCTOR for the PopupWindow object
// Pass it a DIV name to use a DHTML popup, otherwise will default to window popup
function PopupWindow() {
	if (!window.popupWindowIndex) { window.popupWindowIndex = 0; }
	if (!window.popupWindowObjects) { window.popupWindowObjects = new Array(); }
	if (!window.listenerAttached) {
		window.listenerAttached = true;
		PopupWindow_attachListener();
		}
	this.index = popupWindowIndex++;
	popupWindowObjects[this.index] = this;
	this.divName = null;
	this.popupWindow = null;
	this.width=0;
	this.height=0;
	this.populated = false;
	this.visible = false;
	this.autoHideEnabled = false;
	
	this.contents = "";
	this.url="";
	this.windowProperties="toolbar=no,location=no,status=no,menubar=no,scrollbars=auto,resizable,alwaysRaised,dependent,titlebar=no";
	if (arguments.length>0) {
		this.type="DIV";
		this.divName = arguments[0];
		}
	else {
		this.type="WINDOW";
		}
	this.use_gebi = false;
	this.use_css = false;
	this.use_layers = false;
	if (document.getElementById) { this.use_gebi = true; }
	else if (document.all) { this.use_css = true; }
	else if (document.layers) { this.use_layers = true; }
	else { this.type = "WINDOW"; }
	this.offsetX = 0;
	this.offsetY = 0;
	// Method mappings
	this.getXYPosition = PopupWindow_getXYPosition;
	this.populate = PopupWindow_populate;
	this.setUrl = PopupWindow_setUrl;
	this.setWindowProperties = PopupWindow_setWindowProperties;
	this.refresh = PopupWindow_refresh;
	this.showPopup = PopupWindow_showPopup;
	this.hidePopup = PopupWindow_hidePopup;
	this.setSize = PopupWindow_setSize;
	this.isClicked = PopupWindow_isClicked;
	this.autoHide = PopupWindow_autoHide;
	this.hideIfNotClicked = PopupWindow_hideIfNotClicked;
	}


// ===================================================================
// Author: Matt Kruse <matt@mattkruse.com>
// WWW: http://www.mattkruse.com/
//
// NOTICE: You may use this code for any purpose, commercial or
// private, without any further permission from the author. You may
// remove this notice from your final code if you wish, however it is
// appreciated by the author if at least my web site address is kept.
//
// You may *NOT* re-distribute this code in any way except through its
// use. That means, you can include it in your product, or your web
// site, or any other form where the code is actually being used. You
// may not put the plain javascript up on your site for download or
// include it in your javascript libraries for download. 
// If you wish to share this code with others, please just point them
// to the URL instead.
// Please DO NOT link directly to my .js files from your site. Copy
// the files to your server and use them there. Thank you.
// ===================================================================

/* 
AnchorPosition.js
Author: Matt Kruse
Last modified: 10/11/02

DESCRIPTION: These functions find the position of an <A> tag in a document,
so other elements can be positioned relative to it.

COMPATABILITY: Netscape 4.x,6.x,Mozilla, IE 5.x,6.x on Windows. Some small
positioning errors - usually with Window positioning - occur on the 
Macintosh platform.

FUNCTIONS:
getAnchorPosition(anchorname)
  Returns an Object() having .x and .y properties of the pixel coordinates
  of the upper-left corner of the anchor. Position is relative to the PAGE.

getAnchorWindowPosition(anchorname)
  Returns an Object() having .x and .y properties of the pixel coordinates
  of the upper-left corner of the anchor, relative to the WHOLE SCREEN.

NOTES:

1) For popping up separate browser windows, use getAnchorWindowPosition. 
   Otherwise, use getAnchorPosition

2) Your anchor tag MUST contain both NAME and ID attributes which are the 
   same. For example:
   <A NAME="test" ID="test"> </A>

3) There must be at least a space between <A> </A> for IE5.5 to see the 
   anchor tag correctly. Do not do <A></A> with no space.
*/ 

// getAnchorPosition(anchorname)
//   This function returns an object having .x and .y properties which are the coordinates
//   of the named anchor, relative to the page.
function getAnchorPosition(anchorname) {
	// This function will return an Object with x and y properties
	var useWindow=false;
	var coordinates=new Object();
	var x=0,y=0;
	// Browser capability sniffing
	var use_gebi=false, use_css=false, use_layers=false;
	if (document.getElementById) { use_gebi=true; }
	else if (document.all) { use_css=true; }
	else if (document.layers) { use_layers=true; }
	// Logic to find position
 	if (use_gebi && document.all) {
		x=AnchorPosition_getPageOffsetLeft(document.all[anchorname]);
		y=AnchorPosition_getPageOffsetTop(document.all[anchorname]);
		}
	else if (use_gebi) {
		var o=document.getElementById(anchorname);
		x=AnchorPosition_getPageOffsetLeft(o);
		y=AnchorPosition_getPageOffsetTop(o);
		}
 	else if (use_css) {
		x=AnchorPosition_getPageOffsetLeft(document.all[anchorname]);
		y=AnchorPosition_getPageOffsetTop(document.all[anchorname]);
		}
	else if (use_layers) {
		var found=0;
		for (var i=0; i<document.anchors.length; i++) {
			if (document.anchors[i].name==anchorname) { found=1; break; }
			}
		if (found==0) {
			coordinates.x=0; coordinates.y=0; return coordinates;
			}
		x=document.anchors[i].x;
		y=document.anchors[i].y;
		}
	else {
		coordinates.x=0; coordinates.y=0; return coordinates;
		}
	coordinates.x=x;
	coordinates.y=y;
	return coordinates;
	}

// getAnchorWindowPosition(anchorname)
//   This function returns an object having .x and .y properties which are the coordinates
//   of the named anchor, relative to the window
function getAnchorWindowPosition(anchorname) {
	var coordinates=getAnchorPosition(anchorname);
	var x=0;
	var y=0;
	if (document.getElementById) {
		if (isNaN(window.screenX)) {
			x=coordinates.x-document.documentElement.scrollLeft+window.screenLeft;
			y=coordinates.y-document.documentElement.scrollTop+window.screenTop;
			}
		else {
			x=coordinates.x+window.screenX+(window.outerWidth-window.innerWidth)-window.pageXOffset;
			y=coordinates.y+window.screenY+(window.outerHeight-24-window.innerHeight)-window.pageYOffset;
			}
		}
	else if (document.all) {
		x=coordinates.x-document.body.scrollLeft+window.screenLeft;
		y=coordinates.y-document.body.scrollTop+window.screenTop;
		}
	else if (document.layers) {
		x=coordinates.x+window.screenX+(window.outerWidth-window.innerWidth)-window.pageXOffset;
		y=coordinates.y+window.screenY+(window.outerHeight-24-window.innerHeight)-window.pageYOffset;
		}
	coordinates.x=x;
	coordinates.y=y;
	return coordinates;
	}

// Functions for IE to get position of an object
function AnchorPosition_getPageOffsetLeft (el) {
	var ol=el.offsetLeft;
	while ((el=el.offsetParent) != null) { 
    ol += el.offsetLeft; 
    }
	return ol;
	}
function AnchorPosition_getWindowOffsetLeft (el) {
	return AnchorPosition_getPageOffsetLeft(el)-document.body.scrollLeft;
	}	
function AnchorPosition_getPageOffsetTop (el) {
	var ot=el.offsetTop;
	while((el=el.offsetParent) != null) { 
    ot += el.offsetTop; 
    }
	return ot;
	}
function AnchorPosition_getWindowOffsetTop (el) {
	return AnchorPosition_getPageOffsetTop(el)-document.body.scrollTop;
	}

// ===================================================================
// Author: Matt Kruse <matt@mattkruse.com>
// WWW: http://www.mattkruse.com/
//
// NOTICE: You may use this code for any purpose, commercial or
// private, without any further permission from the author. You may
// remove this notice from your final code if you wish, however it is
// appreciated by the author if at least my web site address is kept.
//
// You may *NOT* re-distribute this code in any way except through its
// use. That means, you can include it in your product, or your web
// site, or any other form where the code is actually being used. You
// may not put the plain javascript up on your site for download or
// include it in your javascript libraries for download. 
// If you wish to share this code with others, please just point them
// to the URL instead.
// Please DO NOT link directly to my .js files from your site. Copy
// the files to your server and use them there. Thank you.
// ===================================================================

// HISTORY
// ------------------------------------------------------------------
// May 17, 2003: Fixed bug in parseDate() for dates <1970
// March 11, 2003: Added parseDate() function
// March 11, 2003: Added "NNN" formatting option. Doesn't match up
//                 perfectly with SimpleDateFormat formats, but 
//                 backwards-compatability was required.

// ------------------------------------------------------------------
// These functions use the same 'format' strings as the 
// java.text.SimpleDateFormat class, with minor exceptions.
// The format string consists of the following abbreviations:
// 
// Field        | Full Form          | Short Form
// -------------+--------------------+-----------------------
// Year         | yyyy (4 digits)    | yy (2 digits), y (2 or 4 digits)
// Month        | MMM (name or abbr.)| MM (2 digits), M (1 or 2 digits)
//              | NNN (abbr.)        |
// Day of Month | dd (2 digits)      | d (1 or 2 digits)
// Day of Week  | EE (name)          | E (abbr)
// Hour (1-12)  | hh (2 digits)      | h (1 or 2 digits)
// Hour (0-23)  | HH (2 digits)      | H (1 or 2 digits)
// Hour (0-11)  | KK (2 digits)      | K (1 or 2 digits)
// Hour (1-24)  | kk (2 digits)      | k (1 or 2 digits)
// Minute       | mm (2 digits)      | m (1 or 2 digits)
// Second       | ss (2 digits)      | s (1 or 2 digits)
// AM/PM        | a                  |
//
// NOTE THE DIFFERENCE BETWEEN MM and mm! Month=MM, not mm!
// Examples:
//  "MMM d, y" matches: January 01, 2000
//                      Dec 1, 1900
//                      Nov 20, 00
//  "M/d/yy"   matches: 01/20/00
//                      9/2/00
//  "MMM dd, yyyy hh:mm:ssa" matches: "January 01, 2000 12:30:45AM"
// ------------------------------------------------------------------

var MONTH_NAMES=new Array('January','February','March','April','May','June','July','August','September','October','November','December','Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');
var DAY_NAMES=new Array('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sun','Mon','Tue','Wed','Thu','Fri','Sat');
function LZ(x) {return(x<0||x>9?"":"0")+x}

// ------------------------------------------------------------------
// isDate ( date_string, format_string )
// Returns true if date string matches format of format string and
// is a valid date. Else returns false.
// It is recommended that you trim whitespace around the value before
// passing it to this function, as whitespace is NOT ignored!
// ------------------------------------------------------------------
function isDate(val,format) {
	var date=getDateFromFormat(val,format);
	if (date==0) { return false; }
	return true;
	}

// -------------------------------------------------------------------
// compareDates(date1,date1format,date2,date2format)
//   Compare two date strings to see which is greater.
//   Returns:
//   1 if date1 is greater than date2
//   0 if date2 is greater than date1 of if they are the same
//  -1 if either of the dates is in an invalid format
// -------------------------------------------------------------------
function compareDates(date1,dateformat1,date2,dateformat2) {
	var d1=getDateFromFormat(date1,dateformat1);
	var d2=getDateFromFormat(date2,dateformat2);
	if (d1==0 || d2==0) {
		return -1;
		}
	else if (d1 > d2) {
		return 1;
		}
	return 0;
	}

// ------------------------------------------------------------------
// formatDate (date_object, format)
// Returns a date in the output format specified.
// The format string uses the same abbreviations as in getDateFromFormat()
// ------------------------------------------------------------------
function formatDate(date,format) {
	format=format+"";
	var result="";
	var i_format=0;
	var c="";
	var token="";
	var y=date.getYear()+"";
	var M=date.getMonth()+1;
	var d=date.getDate();
	var E=date.getDay();
	var H=date.getHours();
	var m=date.getMinutes();
	var s=date.getSeconds();
	var yyyy,yy,MMM,MM,dd,hh,h,mm,ss,ampm,HH,H,KK,K,kk,k;
	// Convert real date parts into formatted versions
	var value=new Object();
	if (y.length < 4) {y=""+(y-0+1900);}
	value["y"]=""+y;
	value["yyyy"]=y;
	value["yy"]=y.substring(2,4);
	value["M"]=M;
	value["MM"]=LZ(M);
	value["MMM"]=MONTH_NAMES[M-1];
	value["NNN"]=MONTH_NAMES[M+11];
	value["d"]=d;
	value["dd"]=LZ(d);
	value["E"]=DAY_NAMES[E+7];
	value["EE"]=DAY_NAMES[E];
	value["H"]=H;
	value["HH"]=LZ(H);
	if (H==0){value["h"]=12;}
	else if (H>12){value["h"]=H-12;}
	else {value["h"]=H;}
	value["hh"]=LZ(value["h"]);
	if (H>11){value["K"]=H-12;} else {value["K"]=H;}
	value["k"]=H+1;
	value["KK"]=LZ(value["K"]);
	value["kk"]=LZ(value["k"]);
	if (H > 11) { value["a"]="PM"; }
	else { value["a"]="AM"; }
	value["m"]=m;
	value["mm"]=LZ(m);
	value["s"]=s;
	value["ss"]=LZ(s);
	while (i_format < format.length) {
		c=format.charAt(i_format);
		token="";
		while ((format.charAt(i_format)==c) && (i_format < format.length)) {
			token += format.charAt(i_format++);
			}
		if (value[token] != null) { result=result + value[token]; }
		else { result=result + token; }
		}
	return result;
	}
	
// ------------------------------------------------------------------
// Utility functions for parsing in getDateFromFormat()
// ------------------------------------------------------------------
function _isInteger(val) {
	var digits="1234567890";
	for (var i=0; i < val.length; i++) {
		if (digits.indexOf(val.charAt(i))==-1) { return false; }
		}
	return true;
	}
function _getInt(str,i,minlength,maxlength) {
	for (var x=maxlength; x>=minlength; x--) {
		var token=str.substring(i,i+x);
		if (token.length < minlength) { return null; }
		if (_isInteger(token)) { return token; }
		}
	return null;
	}
	
// ------------------------------------------------------------------
// getDateFromFormat( date_string , format_string )
//
// This function takes a date string and a format string. It matches
// If the date string matches the format string, it returns the 
// getTime() of the date. If it does not match, it returns 0.
// ------------------------------------------------------------------
function getDateFromFormat(val,format) {
	val=val+"";
	format=format+"";
	var i_val=0;
	var i_format=0;
	var c="";
	var token="";
	var token2="";
	var x,y;
	var now=new Date();
	var year=now.getYear();
	var month=now.getMonth()+1;
	var date=1;
	var hh=now.getHours();
	var mm=now.getMinutes();
	var ss=now.getSeconds();
	var ampm="";
	
	while (i_format < format.length) {
		// Get next token from format string
		c=format.charAt(i_format);
		token="";
		while ((format.charAt(i_format)==c) && (i_format < format.length)) {
			token += format.charAt(i_format++);
			}
		// Extract contents of value based on format token
		if (token=="yyyy" || token=="yy" || token=="y") {
			if (token=="yyyy") { x=4;y=4; }
			if (token=="yy")   { x=2;y=2; }
			if (token=="y")    { x=2;y=4; }
			year=_getInt(val,i_val,x,y);
			if (year==null) { return 0; }
			i_val += year.length;
			if (year.length==2) {
				if (year > 70) { year=1900+(year-0); }
				else { year=2000+(year-0); }
				}
			}
		else if (token=="MMM"||token=="NNN"){
			month=0;
			for (var i=0; i<MONTH_NAMES.length; i++) {
				var month_name=MONTH_NAMES[i];
				if (val.substring(i_val,i_val+month_name.length).toLowerCase()==month_name.toLowerCase()) {
					if (token=="MMM"||(token=="NNN"&&i>11)) {
						month=i+1;
						if (month>12) { month -= 12; }
						i_val += month_name.length;
						break;
						}
					}
				}
			if ((month < 1)||(month>12)){return 0;}
			}
		else if (token=="EE"||token=="E"){
			for (var i=0; i<DAY_NAMES.length; i++) {
				var day_name=DAY_NAMES[i];
				if (val.substring(i_val,i_val+day_name.length).toLowerCase()==day_name.toLowerCase()) {
					i_val += day_name.length;
					break;
					}
				}
			}
		else if (token=="MM"||token=="M") {
			month=_getInt(val,i_val,token.length,2);
			if(month==null||(month<1)||(month>12)){return 0;}
			i_val+=month.length;}
		else if (token=="dd"||token=="d") {
			date=_getInt(val,i_val,token.length,2);
			if(date==null||(date<1)||(date>31)){return 0;}
			i_val+=date.length;}
		else if (token=="hh"||token=="h") {
			hh=_getInt(val,i_val,token.length,2);
			if(hh==null||(hh<1)||(hh>12)){return 0;}
			i_val+=hh.length;}
		else if (token=="HH"||token=="H") {
			hh=_getInt(val,i_val,token.length,2);
			if(hh==null||(hh<0)||(hh>23)){return 0;}
			i_val+=hh.length;}
		else if (token=="KK"||token=="K") {
			hh=_getInt(val,i_val,token.length,2);
			if(hh==null||(hh<0)||(hh>11)){return 0;}
			i_val+=hh.length;}
		else if (token=="kk"||token=="k") {
			hh=_getInt(val,i_val,token.length,2);
			if(hh==null||(hh<1)||(hh>24)){return 0;}
			i_val+=hh.length;hh--;}
		else if (token=="mm"||token=="m") {
			mm=_getInt(val,i_val,token.length,2);
			if(mm==null||(mm<0)||(mm>59)){return 0;}
			i_val+=mm.length;}
		else if (token=="ss"||token=="s") {
			ss=_getInt(val,i_val,token.length,2);
			if(ss==null||(ss<0)||(ss>59)){return 0;}
			i_val+=ss.length;}
		else if (token=="a") {
			if (val.substring(i_val,i_val+2).toLowerCase()=="am") {ampm="AM";}
			else if (val.substring(i_val,i_val+2).toLowerCase()=="pm") {ampm="PM";}
			else {return 0;}
			i_val+=2;}
		else {
			if (val.substring(i_val,i_val+token.length)!=token) {return 0;}
			else {i_val+=token.length;}
			}
		}
	// If there are any trailing characters left in the value, it doesn't match
	if (i_val != val.length) { return 0; }
	// Is date valid for month?
	if (month==2) {
		// Check for leap year
		if ( ( (year%4==0)&&(year%100 != 0) ) || (year%400==0) ) { // leap year
			if (date > 29){ return 0; }
			}
		else { if (date > 28) { return 0; } }
		}
	if ((month==4)||(month==6)||(month==9)||(month==11)) {
		if (date > 30) { return 0; }
		}
	// Correct hours value
	if (hh<12 && ampm=="PM") { hh=hh-0+12; }
	else if (hh>11 && ampm=="AM") { hh-=12; }
	var newdate=new Date(year,month-1,date,hh,mm,ss);
	return newdate.getTime();
	}

// ------------------------------------------------------------------
// parseDate( date_string [, prefer_euro_format] )
//
// This function takes a date string and tries to match it to a
// number of possible date formats to get the value. It will try to
// match against the following international formats, in this order:
// y-M-d   MMM d, y   MMM d,y   y-MMM-d   d-MMM-y  MMM d
// M/d/y   M-d-y      M.d.y     MMM-d     M/d      M-d
// d/M/y   d-M-y      d.M.y     d-MMM     d/M      d-M
// A second argument may be passed to instruct the method to search
// for formats like d/M/y (european format) before M/d/y (American).
// Returns a Date object or null if no patterns match.
// ------------------------------------------------------------------
function parseDate(val) {
	var preferEuro=(arguments.length==2)?arguments[1]:false;
	generalFormats=new Array('y-M-d','MMM d, y','MMM d,y','y-MMM-d','d-MMM-y','MMM d');
	monthFirst=new Array('M/d/y','M-d-y','M.d.y','MMM-d','M/d','M-d');
	dateFirst =new Array('d/M/y','d-M-y','d.M.y','d-MMM','d/M','d-M');
	var checkList=new Array('generalFormats',preferEuro?'dateFirst':'monthFirst',preferEuro?'monthFirst':'dateFirst');
	var d=null;
	for (var i=0; i<checkList.length; i++) {
		var l=window[checkList[i]];
		for (var j=0; j<l.length; j++) {
			d=getDateFromFormat(val,l[j]);
			if (d!=0) { return new Date(d); }
			}
		}
	return null;
	}

/*
	navigationUtil.js 
	-Utility functions for navigation (tabs, etc.)
*/
//alert('navigationUtil.js loaded');

  function navigateNextNode(nextNodeVal, field, theForm)
  {
	  //alert('navigatenextnode');
	field.value=nextNodeVal;
//	alert('value set');
	//alert('field is ['+field+'] form is ['+theForm+']');
	theForm.submit();
  }

var formChanged = false;

function changed(){
    formChanged = true;
}

function saveForm() {
    var answer= true;
    if(formChanged) {
        answer = confirm("You are about to lose the changes you made. Click cancel to go back and save the form");
        if(answer) {
            return true;
        }
        else {
            return false;
        }
    }

}
//this takes in id and disables text and removes 
function disableForm(formId){
  var allElems=document.getElementById(formId).elements;
  var toRemove=new Array(0);
  for(var k=0; k<allElems.length; k++){
    if(allElems[k].type=="button" || allElems[k].type=="submit"){
      toRemove[toRemove.length]=allElems[k];
    }else{
    allElems[k].disabled=true;
    }
  }
  for(var k=0; k<toRemove.length; k++){
    toRemove[k].parentNode.removeChild(toRemove[k]);
  }
}

function disableSubmit(elem){
  var elems=elem.elements;
  for( var k=0; k< elems.length; k++){
    if(elems[k].type=="submit"){
      elems[k].disabled=true;
    }
  }
}


function addUpdateTokens(asiQueryString, formElem){
  var expression=/(\$token)=([A-Za-z0-9]*)&(\$form_state)=([a-zA-Z0-9]*)/;
  var results=asiQueryString.match(expression);
  
  for(var k=1; k<results.length; k+=2){
    var hidden=document.createElement("input");
    hidden.type="hidden";
    hidden.name=results[k];
    hidden.value=results[k+1];
    formElem.appendChild(hidden);
  }

}




// open a add member pop-up window
function popWin (url,wd,hg,lf,top) 
{
  opf='toolbar=no,top='+top+',left='+lf+',screenY='+top+',screenX='+lf+',width='+wd+',height='+hg+',scrollbars=no';
  info=window.open(url,'info',opf);info.focus();
  if (memberWindow.opener == null) memberWindow.opener = self;

}


// update the team details page with the information on the pop-up

function updateTeam() {
    opener.document.teamForm.ttlupi.value = document.memberForm.tryone.value;
    self.close();
    return false;
}

// open the user guide window
function popWinHelp (url,wd,hg,lf,top) {
  opf='toolbar=no,top='+top+',left='+lf+',screenY='+top+',screenX='+lf+',width='+wd+',height='+hg+',scrollbars=no,resizable=yes';
  info=window.open(url,'info',opf);info.focus();
}

// displays wait gif for user when next page is loading
function waitForPage(waitTarget) { 
  waitTarget.innerHTML = asi.WAIT;
  waitTarget.childNodes[0].style.height = '60px';
}

function waitForPage(formElement, waitTarget) {
  scroll(0,0);
  disableSubmit(formElement);
  waitTarget.innerHTML = asi.WAIT;
  waitTarget.childNodes[0].style.height = '60px';
}

























var DATA_CAP_WORDS = 50;
var DATA_CAP_SENTENCES = 200;
var DATA_CAP_PARAGRAPHS = 1500;

var CONTEXT_PREFIX = '/suite/';

var BACKSPACE_KEY = 8;
var ENTER_KEY = 13;
var ESC_KEY = 27;
var PG_UP_KEY = 33;
var PG_DOWN_KEY = 34;
var UP_ARROW_KEY = 38;
var DOWN_ARROW_KEY = 40;
var DELETE_KEY = 46;
var F10_KEY = 121;
var F12_KEY = 123;
var SCRLK_KEY = 145;

var PANE_MINIMIZE = 0;
var PANE_RESTORE = 1;
var PANE_MAXIMIZE = 2;

var LOG_ERROR = 0;
var LOG_WARN = 1;
var LOG_DEBUG = 2;

var CLIENT_LOG_LEVEL = LOG_DEBUG;
var LOG_CLIENT_ERRORS = true;
var TRAP_CLIENT_ERRORS = false;
var LOG_IMPORT_STATEMENTS = false;

var TYPE_DEFERRED = 0;
var TYPE_LONG = 1;
var TYPE_DOUBLE = 2;
var TYPE_STRING = 3;
var TYPE_USER = 4;
var TYPE_GROUP = 5;
var TYPE_CURRENCY = 6;
var TYPE_DATE = 7;
var TYPE_TIME = 8;
var TYPE_DATETIME = 9;
//var TYPE_BINARY = ;
var TYPE_BEAN = 11;
var TYPE_FOLDER = 12;
var TYPE_DOCUMENT = 13;
var TYPE_PAGE = 15;
var TYPE_FORUM = 16;
var TYPE_DISCUSSION_THREAD= 17;
var TYPE_MESSAGE = 18;
var TYPE_KNOWLEDGE_CENTER= 19;
var TYPE_COMMUNITY  = 20;
var TYPE_TASK  = 21;
var TYPE_PROCESS  = 22;
var TYPE_TEMPLATE  = 23;
var TYPE_PROCESS_MODEL = 23;
var TYPE_ATTACHMENT  = 24;
var TYPE_NOTE  = 31;
var TYPE_ROLE  = 25;
var TYPE_BOOLEAN  = 26;
var TYPE_PEOPLE  = 27;
var TYPE_CONTENT  = 28;
var TYPE_PROCESSMODEL_FOLDER  = 200;
var TYPE_DURATION  = 29;
var TYPE_SIM_SCENARIO  = 30;
var TYPE_PASSWORD = 32;

var TYPES =  [         ['Content', TYPE_CONTENT],
                       ['Collaboration Community', TYPE_COMMUNITY],
                       ['Date', TYPE_DATE],
                       ['Date and Time', TYPE_DATETIME],
                       ['Decimal', TYPE_DOUBLE],
					   ['Deferred', TYPE_DEFERRED],
					   ['Document', TYPE_DOCUMENT],
                       ['Folder', TYPE_FOLDER],
                       ['Forum', TYPE_FORUM],
                       ['Group', TYPE_GROUP],
                       ['Knowledge Center', TYPE_KNOWLEDGE_CENTER],
                       ['Message', TYPE_MESSAGE],
                       ['Number', TYPE_LONG],
                       ['People', TYPE_PEOPLE],
                       ['Portal Page', TYPE_PAGE],
//                       ['Portlet', TYPE_PORTLET],
                       ['Record', TYPE_BEAN],
                       ['Text', TYPE_STRING],
                       ['Time', TYPE_TIME],
                       ['Topic', TYPE_DISCUSSION_THREAD],
                       ['User', TYPE_USER],
                       ['Yes/No', TYPE_BOOLEAN]];

var NON_DESIGN_TIME_TYPES = [
                       ['Content', TYPE_CONTENT],
                       ['Collaboration Community', TYPE_COMMUNITY],
                       ['Date', TYPE_DATE],
                       ['Date and Time', TYPE_DATETIME],
                       ['Decimal', TYPE_DOUBLE],
                       ['Document', TYPE_DOCUMENT],
                       ['Folder', TYPE_FOLDER],
                       ['Forum', TYPE_FORUM],
                       ['Group', TYPE_GROUP],
                       ['Knowledge Center', TYPE_KNOWLEDGE_CENTER],
                       ['Message', TYPE_MESSAGE],
                       ['Number', TYPE_LONG],
                       ['Password', TYPE_PASSWORD],
                       ['People', TYPE_PEOPLE],
                       ['Portal Page', TYPE_PAGE],
//                       ['Portlet', TYPE_PORTLET],
                       ['Text', TYPE_STRING],
                       ['Time', TYPE_TIME],
                       ['Topic', TYPE_DISCUSSION_THREAD],
                       ['User', TYPE_USER],
                       ['Yes/No', TYPE_BOOLEAN]];

var DEFAULT_ROLE = 1;
var EDITOR_ROLE = 2;
var ADMIN_ROLE = 3;

var asiArrowUpURL = CONTEXT_PREFIX + 'portal/img/nav_uparrow.gif';
var asiArrowDownURL = CONTEXT_PREFIX + 'portal/img/nav_downarrow.gif';

var strUserIconPath = CONTEXT_PREFIX + 'portal/img/perm_user_icon.gif';
var strGroupIconPath = CONTEXT_PREFIX + 'portal/img/perm_group_icon.gif';
var strPortalGroupIconPath = CONTEXT_PREFIX + 'portal/img/group_icon.gif';

var linkhandlerPath = CONTEXT_PREFIX + 'portal/linkhandler.do';
var strNotifActionPath = CONTEXT_PREFIX + 'portal/viewnotification.none';
var strACDocumentExternalPath = CONTEXT_PREFIX + 'collaboration/downloadDocumentExternal.do';

var strPageExternalPath = CONTEXT_PREFIX + 'getPageExternal.do';
var strForumExternalPath = CONTEXT_PREFIX + 'getForumExternal.do';
var strThreadExternalPath = CONTEXT_PREFIX + 'getThreadExternal.do';
var strAPGroupExternalPath = CONTEXT_PREFIX + 'personalization/getGroupExternal.do';
var strAPUserExternalPath = CONTEXT_PREFIX + 'personalization/getUserExternal.do';



var strACKnowledgeCenterExternalPath = CONTEXT_PREFIX + 'knowledge/ViewKnowledgeCenterContents.do'; 
var strACFolderExternalPath = CONTEXT_PREFIX + 'knowledge/ViewFolderContents.do';

var shortMonths = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
var longMonths = ['January','February','March','April','May','June','July','August','September','October','November','December'];
/**
* ASI Component core object
* 
* This behaves as the 'superclass' of all the other components. In order to extend
* this object into a particular component, it needs to be instantiated from the child
* component's constructor.
* 
* As a parent object, it imports the stylesheets and scripts that the child component
* needs. It also appends a 'type' attribute to it and finds the DOM for it. Some 
* components don't have a DOM container because they generate their DOM and
* position it absolutely in the document.
* @since 4.0
*/
function Component(id, stylesheets, type) {

  this.scripts = new Array();

  this.stylesheets = stylesheets || new Array();
  this.type = type || 'Component';

  this.id = id || (type + new Date().getTime());

  this.DOM = getObject(id);
  if (this.DOM != null) this.DOM.setAttribute('componentType', this.type);    

  for (var i = 0; i < this.stylesheets.length; i++) {
    importStyleSheet(this.stylesheets[i]);
  } 

  for (var i = 0; i < this.scripts.length; i++) {
    importScript(this.scripts[i]);
  }
  /**
  * Removes the DOM object from the document
  */
  this.hide = function () {
   if(this.DOM.parentNode)  this.DOM.parentNode.removeChild(this.DOM); 
  }

  /**
  * Removes the DOM object from the document if the cursor is effectively
  * out of the object. As the mouse is moving on top of the object, the onmouseout and
  * onmouseover are constantly triggered even though the mouse is not necessarily out of
  * the DOM area. This delay ensures that the mouse is indeed out of the DOM area before hiding
  * the component.
  */
  this.delayedHide = function () {
    if (!this.DOM.className.match(/hover/g)) this.hide();
  }

  /**
  * Attaches mouse events to component so it shows and hides according to mouse 
  * movement. 
  * 
  * @param id the id of the document object to attach the listeners to. This id MUST
  *                   match the id of the component instance itself.
  * @since 4.1
  */
  this.attachEvents = function(id) {
    var element = getObject(id);
    element.onmouseover = function () {
      this.className+=" hover";
    }
    element.onmouseout = function () {
      this.className=this.className.replace(/ *hover/g, "");
      var f = 'window.' + id + '.delayedHide()';
      setTimeout(f, 5000);
    }
  }

  /**
  * Filters the items according to the attributes: showForTypes
  * hideForMultiple (boolean), and permissions. All items will be shown or 
  * hidden given these attributes within them, depending on the type that the
  * row represents and the permissions that it allows.
  * 
  * An item or action that does not have the showForTypes attribute
  * will always be displayed.
  * 
  * An item with a showForTypes attribute defined will only display if there is a
  * row with that type. However, if there is another type selected as well
  * the item will not show. This behavior can be summarized in the following way:
  * If the row types are a subset or equal to the showForTypes attribute,
  * the item will be shown, otherwise it will be hidden UNLESS there is no
  * attribute declared, in which case it will always show.
  * 
  * If an item has the hideForMultiple attribute equal to "true", the item will only
  * render when there is one and only one row in the array.
  * 
  * The permissions assume that the credentials are within the row's descriptor.
  * See documentation on the Grid component for a more extensive definition.
  * The syntax to access these permissions are to enclose in brackets the index of
  * the permission within the descriptor, such that [4] corresponds to
  * descriptor.tokenAt(4). Complex boolean operations are possible, for example:
  * [4] && ([8] == '' || [8] == '0')
  * 
  * The function executes by first evaluating for hideForMultiple, then it checks
  * the permissions, and finally checks the showForTypes attribute .
  * @since 4.0
  */
  this.filterActionItems = function (items, rows) {
      var descriptors = GRID.getRowDescriptors(rows);
      var selected = GRID.getKeys(descriptors);
      var types = new Array();
      for (var i = 0; i < selected.length; i++) {
        var type = selected[i].tokenAt(0);
        if (types.indexOf(type) == -1) types.push(type);
      }
      for (var i = 0; i < items.length; i++) {
        var item = items[i];
        hideForMultiple = item.hideForMultiple || item.getAttribute('hideForMultiple');
        showForTypes = item.showForTypes || item.getAttribute('showForTypes');
        item.style.display = '';
        if ((hideForMultiple == "true" && rows.length > 1) ||
          !isActionPermitted(descriptors, item) ||
          (showForTypes && rows.length == 0)){
          item.style.display = 'none';
          continue;
        }  
        if (showForTypes && rows.length > 0) {
          for (var j = 0; j < types.length; j++) {
            if (!isTypeInItem(types[j], item)) {
              item.style.display = 'none';
              break;
            }
          }          
        }
      }
    /*
    * Inner function for filterActionItems.
    * Evaluates the "showForTypes" attribute in the given item (can be a link in a
    * toolbar or an LI in a dropdown, for example), and compares it to the given type.
    * Either the string representation of the constant can be used or the number it
    * represents, for the attribute or for the descriptor.
    * It is recommendable to use the string in the attribute, however, as it makes it more
    * readable. A warning will be issued if the number is encountered instead.
    * @since 4.0
    */
    function isTypeInItem(type, item) {
      var showForTypes = item.showForTypes || item.getAttribute('showForTypes');
      showForTypes = showForTypes.split(",");
      for (var i = 0; i < showForTypes.length; i++) {
        var showForType = showForTypes[i].trim();
        var translatedType = eval(showForType);
        if (translatedType == eval(type)){
          if (!showForType.match(/\D/)) {
            var msg = 'Encountered literal object type. %0a';
            msg += 'The string representation of the type constant should be used ';            
            msg += 'instead of the number that it represents (TYPE ' + type + ').';
            ASI_LOG.warn(msg, true);            
          }
          return true;
        }
      }
      return false;
    }

    /*
    * Inner function for filterActionItems.
    * Translates the value of the permissions attribute into a boolean. These attributes
    * reference the descriptor items by index. These indices are expressed as numbers
    * within brackets: [i], and get translated as: descriptor.tokenAt(i). The permissions
    * are compared to each of the descriptors and returns false as soon as any
    * comparison evaluates to false.
    * 
    * If the item has no permissions attribute, the function will return true. In order for
    * the function to return true, either no permission restrictions are defined for the
    * item, or all of the descriptors allow that action.
    * @since 4.0
    */
    function isActionPermitted(descriptors, item) {
      if (!(descriptors instanceof Array)) throw new Error('Array expected');
      permissions = item.permissions || item.getAttribute('permissions');
      if (!permissions) return true;
      var isAllowed = permissions;
      isAllowed = isAllowed.replace(/\[([$\w\.]+)\]/g, '(function(){try{return eval(descriptor.tokenAt(eval($1)))}catch(e){return descriptor.tokenAt(eval($1))}})()');
      for (var i = 0; i < descriptors.length; i++) {
        var descriptor = descriptors[i];
        if (!eval(isAllowed)) return false;
      }
      return true;
    }
  }

}
/**
* ASI Dropdown Component v2.0
* @since 4.0
*/
function Dropdown(id) {
  this.stylesheets = ['/components/dropdown/css/asiDropdown.css'];
  this.className = 'asiDropdown';
  this.type = 'Dropdown';

  this.component = Component;
  this.component(id, this.stylesheets, this.type);
  
  var initDropdown = init(this.DOM);
  if (initDropdown) this.DOM = initDropdown;
  else throw new Error('Dropdown component "' + id + '" not found');

  attachEvents(this.DOM);

  /**
  * Attach events to the component to activate and deactivate it
  * @since 4.0
  */
  function attachEvents(element) {
      attachHover(element, 'LI', true);
    /*   var elements = getNodeElements(element.childNodes);
      for (var i = 0; i < elements.length; i++) {
        var li = elements[i];
        li.onmouseover = function(){return true}
        li.onclick = function(){this.className += ' hover'}
      } */
  }
  /**
  * @return the UL element if a dropdown is found, null otherwise;
  * @since 4.0
  */
  function init(element) {
    var ul = element.tagName == 'UL' ? element : element.getElementsByTagName('UL')[0];
    if (!ul) return null;
    if (ul.className.match(/asiDropDown/)) return ul;
    return null;
  }
}
/**
* ASI Context Menu Component
* 
* To use this component, put the HTML for an asiDropDown on your page.  When the
* page loads execute "var x = new ContextMenu(x); x.hide()", where x is the id
* of the dropdown's outer ul's parent node .  Give an element on your page
* oncontextmenu="dropdownContainer.show(event)".  When this
* element is right-clicked on, the contextMenu will activate.
*
* @param id The id of the dropdown's outer ul's parent node 
* @since 4.0
*/
function ContextMenu(id) {
  this.stylesheets = ['/components/dropdown/css/asiDropdown.css'];
  this.className = 'asiDropDown';
  this.type = 'ContextMenu';

  this.component = Component;
  this.component(id, this.stylesheets, this.type);

  var initMenu = init(this.DOM);
  if (!initMenu) throw new Error('Context Menu ' + id + ' not found');
  this.itemAnchor = this.DOM.getElementsByTagName('LI')[0];
  CONTEXTMENU.hideAll();
  this.DOM.style.position = 'absolute';
  attachEvents(this.DOM);

/**
* Shows the context menu where the user has clicked. In order to successfully 
* implement this, the event object MUST be explicitly passed to the call. This also
* means that the target object's onclick() method cannot be called for this purpose.
* 
* @since 4.0
*/
  this.show = function (evt) {
    if(this.setCapture) this.setCapture();
    if (!evt) ASI_LOG.warn(EVENT_INCOMPATIBILITY, true);
    evt = (evt) ? evt : (event) ? event : null;
    if (!evt) throw new Error("Function cannot be called without explicit user action");
    this.filterActions(this.DOM, evt);
    document.body.appendChild(this.DOM);
    var x = evt.pageX ? evt.pageX : event.clientX + document.body.scrollLeft;
    var y = evt.pageY ? evt.pageY : event.clientY + document.body.scrollTop;
    this.DOM.style.left = x - 8 + "px";
    this.DOM.style.top = y - 12 + "px";
    this.itemAnchor.className += " hover";
    this.DOM.style.zIndex = 1;
    evt.cancelBubble = true;
    if(evt.preventDefault) evt.preventDefault();
    else evt.returnValue = false;
    return false;
  }

/**
* Removes the DOM object from the document
* @since 4.0
*/
  this.hide = function () {
    this.DOM.parentNode.removeChild(this.DOM);
  }

  /**
  * Removes the DOM object from the document if the cursor is effectively
  * out of the object. When the mouse is moving on top of the object, it triggers
  * the onmouseout event, even though to the user it is not. This delay ensures
  * that the menu is still operational, as it tests if the mouse is still in the area or not.
  * @since 4.0
  */
  this.delayedHide = function () {
    if (!this.itemAnchor.className.match(/hover/g)) this.hide();
  }

  /**
  * Attaches events to component so it shows and hides
  * @since 4.0
  */
  function attachEvents(element) {
    var id = element.id;
    var items = element.getElementsByTagName('LI');
    for (var i = 0; i < items.length; i++) { 
      var item = items[i];
      if (item.className.match(/divider/)) continue;
      item.onmouseover = function () {
        this.className+=" hover";
      }
      item.onmouseout = function () {
        this.className=this.className.replace(/ *hover/g, "");
        setTimeout(id + '.delayedHide()', 500);
      }
    }
  }

  EVENT_INCOMPATIBILITY = "Function must explicitly get the event object for non-IE browsers. Unexpected behavior will stem from this omission.";

  /**
  * Checks if the specified element contains an asiDropDown, by looking
  * for the UL tag with class asiDropDown
  * @return the UL element if found, null otherwise.
  * @since 4.0
  */
  function init(element) {
    if (element.tagName == 'UL' && element.className.match(/asiDropDown/)) return element;
    var ul = getElementsByClassName('asiDropDown', element, 'UL')[0];
    if (ul) return ul;
    else return null;
  }

  /**
  * Filters the actions available in the context menu according to the attributes: showForTypes
  * and hideForMultiple (boolean). The items with these attributes will be
  * shown or hidden automatically this way. Currently, the only way this scheme 
  * can work is if the Context Menu is called for the row of a Grid, as it can only
  * find the types using the row's descriptor.
  * 
  * The type MUST be declared using the string representation of a constant, NOT
  * the number.
  * 
  * This function is called only when the context menu is shown. It cannot be
  * explicitly called.
  * 
  * @see Component - filterActionItems(items, rows) for more information.
  * @since 4.0
  */
  this.filterActions = function (itemContainer, evt) {
    var target = evt.target ? evt.target : evt.srcElement;
    var table = getContainerByTagName(target, 'TABLE');
    var selectedRows = getSelectedRows(table);
    var items = itemContainer.getElementsByTagName('LI');
    this.filterActionItems(items, selectedRows);
  }
}
CONTEXTMENU = {
  /**
  * Hides all ContextMenu objects, removing them from the document.
  * This is sometimes necessary when the timeout will not clean up the context
  * menu (when it gets interrupted by a page load). 
  * @since 4.0
  */
  hideAll : function () {
    var dropdowns = getElementsByClassName('asiDropDown', document, 'UL');
    for (var i = 0; i < dropdowns.length; i++) {
      var item = dropdowns[i];
      if (item.componentType == 'ContextMenu') item.parentNode.removeChild(item);
    }
  }
}
/**
* ASI Grid Component v2.0
* 
* This component is also referred to as the "client grid". This grid relies on a
* descriptor, which is defined as: the value of the checkbox on the first column".
* The descriptor is a string which represents the data on the row. It always begins
* with type and object id, followed by the rest of the relevant data. Each value is
* separated by a forward slash and each token (this string is tokenized using the
* slashes) represents each column in the grid, except for the first two which
* represent the row KEY. 
* 
* Example:
* if row descriptor is: 4/john.doe/Doe, John/8000 Towers Crescent Dr./etc...
* row would look like: Doe, John - 8000 Towers Crescent Dr. - etc...
* 
* Note that the first column, corresponding to the checkbox, is hidden by the
* div with class=asiGridContainer. Also, even though this example uses no markup,
* the markup is assumed to be in the getRowDOM() overriding function, NOT on the
* descriptor itself.
* 
* There are two fundamental ways of implementing this component: 
* a) client only grid; 
* b) hybrid grid.
* 
* a) A client only grid is useful when there is no data on the server, and/or where there 
* is no need for paging nor sorting. This means that all the data that comprises the
* whole grid is in the client. To implement, a container with an id is needed, which
* is then initialized via the constructor such that:
* 
* <div id="myGrid" class="asiGridContainer"></div>
* <script type="text/javascript">
* var myGrid = new Grid("myGrid");
* myGrid.getRowDOM = function(descriptor){ etc...}
* </ script>
* 
* Note that there must be an overriding function for getRowDOM(), otherwise
* the rows will only render the descriptor values. The descriptor values should ONLY
* contain the relevant data of the item, NOT information on how to render it such
* as input tags, images, etc. Basically, NO markup belongs in the descriptor.
* See getRowDOM() function below for more details.
* 
* b) A hybrid grid uses the "server grid" (asi:grid tag) to get the paged data from the
* server. Both the Grid constructor and the getRowDOM() overriding function MUST
* be included in the server grid footer. To initialize, the same concept as above
* prevails, except that the server grid is included inside the grid container:
* 
* <div id="myGrid" class="asiGridContainer">
*   <%--<asi:grid etc... />--%>
* </div>
* 
* The hybrid grid allows only the partial data (current page) to be manipulated on the
* client, while not needing all the information transferred to it. As soon as data is
* modified, a warning appears to indicate that changes will be lost if the data is not
* updated on the server (for example when paging or sorting, which requests new data).
* 
* It is recommended that the function regenerateRows() be called after the getRowDOM()
* declaration on the hybrid grid, to ensure rendering consistency. This also means that
* the way the grid looks should exist only in this function and no longer in the
* grid-config (xml) declaration for it. This declaration might as well just return the
* first column with the descriptors and nothing else (since they will get regenerated).
* 
* To be able to modify the data within a row, see modifyRowData(). This function 
* should be called from the onclick event of the input within the cell, which implies
* having to declare it within the getRowDOM() overriding function. 
* 
* @param id
* @param emptyMessage
* @param delim
* @since 4.0
*/
function Grid(id, emptyMessage, delim) {
  this.stylesheets = ['/components/grid/css/asiGrid.css'];
  this.className = 'asiGrid';
  this.type = 'Grid';

  this.component = Component;
  this.component(id, this.stylesheets, this.type);

  var initGrid = GRID.init(this.DOM);
  this.emptyMessage = emptyMessage || "No results found";
  this.DOM = initGrid ? initGrid : this.DOM.appendChild(GRID.EMPTY_GRID_DOM(id, this.emptyMessage));

  this.defaultView = GRID.DEFAULT_VIEW;
 
  this.delim = delim || "/";
 
  /**
  * Flag that detemines if there have been rows added, modified or deleted.
  * @since 4.0
  */
  this.isChanged = false;
  /**
  * A parallel array containing only the row descriptors.
  * A row descriptor should ALWAYS start with type and id (this is the row key),
  * followed by the column values separated with a delimiter (slash by default).
  * For example, a row like: Mark Hamill - isAdmin - isOwner - isJedi
  * would be represented like: "4/mark.hamill/Hamill, Mark/1/0/1".
  * The descriptor MUST NOT hold display information (such as markup), only data.
  * @since 4.0
  */
  this.descriptors = GRID.getDescriptors(this.DOM);
  /**
  * An array containing the row descriptors of deleted rows
  * @since 4.0
  */
  this.deletedDescriptors = new Array();
  /**
  * Joins added, modified and deleted rows ids (descriptors) into an update string.
  * Deleted items only use the key (type/id), additions and modifications
  * use the whole descriptor.
  * @return a string that joins the ids (descriptors) of the modified rows
  * @since 4.0
  */
  this.getUpdateString = function () {
    var updateString = "";
    var addedRows = getElementsByClassName('added', this.DOM, 'TR');
    var modifiedRows = getElementsByClassName('modified', this.DOM, 'TR');
    var rows = addedRows.concat(modifiedRows);
    for (var i = 0; i < rows.length; i++) {
      var rowDescriptor = GRID.getRowDescriptor(rows[i]);
      if (updateString.indexOf(rowDescriptor.tokenizedSubstring(0, 2, this.delim)) == -1) updateString += escape(rowDescriptor) + ",";
    }
    for (var i = 0; i < this.deletedDescriptors.length; i++) {
      updateString += escape(this.deletedDescriptors[i]) + ",";
    }
    return updateString.replace(/,$/,'');
  }
  /**
  * Indicates the number of columns that comprise the Grid.
  * It should be overridden by normalizeColumns() if used.
  * @since 4.0
  */
  this.numCols = 1;

  /**
  * This function MUST be overridden to define how each row will be rendered.
  * This function takes the descriptor and returns a row object.
  * The first column must always include the descriptor checkbox (see example below).
  * otherwise, the row won't be added and JS errors will be thrown.
  * 
  * If this function is not overridden, it will return a row only with the descriptors.
  * This may be useful if there is nothing but bare text data on the row.
  * 
  * Each item in the array that is passed to the GRID.getRowDOM() function
  * corresponds exactly to each column that is returned. In turn, each token within
  * the descriptor corresponds to each column, except for the first two which are
  * the row key. This key MUST be unique, otherwise the rows won't get properly
  * added to the grid since duplicate keys are not allowed.
  * 
  * Example:
  * 
  * this.getRowDOM = function(descriptor){
  *   var rowData = new Array();
  *   rowData[0] = CheckboxHTML('', descriptor);
  *   rowData[1] = AEItemHTML(descriptor.tokenAt(0), descriptor.tokenAt(1), descriptor.tokenAt(2), true);
  *   rowData[2] = "<input value=\"" + descriptor.tokenAt(3) + "\" onchange=\"myGrid.modifyRowData(this);\" />";
  *   // rowData[n] = f(descriptor.tokenAt(n + 1)); 
  *   var row =  GRID.getRowDOM(rowData);    
  *   row.onclick = function () { selectRow(this) }
  *   return row;
  * }
  * @since 4.0
  */
  this.getRowDOM = function (descriptor){
    var rowData = new Array();
    rowData[0] = CheckboxHTML('', descriptor);
    var data = descriptor.split(delim);
    for (var i = 2; i < data.length; i++) {
      rowData.push(data[i]);
    }
    var row =  GRID.getRowDOM(rowData);    
    row.onclick = function () { selectRow(this) }
    return row;
  }
  /**
  * Re-generates the rows based on their own row descriptors and getRowDOM()
  * This function overrides the changes flag.
  * @since 4.0
  */
  this.regenerateRows = function () {
    var dataRows = getDataRows(this.DOM, "asiGridNoResults");
    for (var i = 0; i < dataRows.length; i++) {
      var rd = GRID.getRowDescriptor(dataRows[i]);
      if (rd) {
        var row = this.addRow(this.getRowDOM(rd), true, true);
        if (row) row.className = row.className.replace(/ added/g,'');
      }
    }
  }
  /**
  * Creates or replaces the grid header given the arguments.
  * The arguments can be DOM objects or strings (innerHTML).
  * @since 4.0
  */
  this.setHeader = function (headerList) {
    var thead = getOrCreateElement('THEAD', this.DOM);
    thead.replaceChild(GRID.getHeaderDOM(arguments), getElementsByClassName('asiGridHeaders', thead, 'TR')[0]);
  }
  /**
  * Creates or replaces the grid caption given a string.
  * @since 4.0
  */
  this.setCaption = function (captionText) {
    var caption = getOrCreateElement('CAPTION', this.DOM);
    caption.innerHTML = captionText;
  }
  /**
  * Creates or replaces the grid toolbar given the arguments.
  * Each of the items in the arguments have to be a particular array
  * that determines the icon, the target (href) and the label like: 
  * ['label', 'href', 'onclick', 'title', 'iconPath']. Only the label is required.
  * Example: ['Back',,'regress()','Go back to basics']
  * @since 4.0
  */
  this.setToolbar = function () {
    var thead = getOrCreateElement('THEAD', this.DOM);
    var tbs = getElementsByClassName('asiGridToolbar', this.DOM, 'TR');
    var toolbarDOM = arguments.length ? GRID.getToolbarDOM(arguments) : GRID.getToolbarDOM();
    if (tbs.length == 0) {
      thead.insertBefore(toolbarDOM, thead.rows[0]);
    } else {
      if (arguments.length == 0) tbs[0].parentNode.removeChild(tbs[0]);
      thead.replaceChild(toolbarDOM, tbs[0]);
    }
    var rowSpace = getElementsByClassName('asiGridRowSpacer', thead, 'TR')[0];
    if (rowSpace) rowSpace.parentNode.removeChild(rowSpace);
  }

  /*
  * Filters the actions available in the toolbar according to the attributes: showForTypes
  * and hideForMultiple (boolean). The items with these attributes will be
  * shown or hidden automatically this way. 
  * 
  * The type MUST be declared using the string representation of a constant, NOT
  * the number.
  * 
  * @see Component - filterActionItems(items, rows) for more information.
  * @since 4.0
  */
  this.filterActions = function () {
    var selectedRows = getSelectedRows(this.DOM);
    var toolbarItems = getToolbarItems(this.DOM);
    this.filterActionItems(toolbarItems, selectedRows);
  }  
  if(window.name != 'fProcess') this.filterActions(); // initial call

  /*
  * Retrieves the anchors and dropdowns from the grid toolbar
  * @return an array with anchor objects and unordered list objects (asiDropDown)
  * @since 4.0
  */
  function getToolbarItems(table) {
    var toolbar = getElementsByClassName('asiGridToolbar', table, 'TR')[0];
    if (typeof toolbar == "undefined") return new Array();
    var items = getNodeElements(toolbar.getElementsByTagName('TD')[0].childNodes);
    for (var i = 0; i < items.length; i++) {
      var item = items[i];
      var isDropDown = (item.tagName == 'UL' && item.className == 'asiDropDown');
      if (item.tagName != 'A' && !isDropDown) items.splice(i, 1);
    }
    return items;
  }

  /**
  * Adds a row to the linked table and adds the descriptor to
  * the descriptor array. If isOverwrite is true and a duplicate id is encountered,
  * the last row to be added will prevail. Otherwise, the first will remain unchanged.
  * 
  * @param rowDOM The row object (TR) to be inserted
  * @param isOverwrite if true, a row with the same key (type/id) will be replaced, 
  * if found. 
  * @param isOverrideWarning (optional) if true, the grid will not display a warning
  * when attempting to sort or page without commiting the changes
  * @param insertAtRow (optional) the row index where this row will be added. If
  * zero, it will add the row as the first row, instead of adding it at the end (default)
  * @return The added row or null if not added (not overwritten)
  * @since 4.0
  */
  this.addRow = function (rowDOM, isOverwrite, isOverrideWarning, insertAtRow){
    if (!rowDOM) return;
    var rowId = GRID.getRowDescriptor(rowDOM);
    var rowIndex = this.getRowIndex(rowDOM);
    var isDuplicate = rowIndex > -1;
    if (isDuplicate && !isOverwrite) return false;
    if(!isOverrideWarning) enableControlsWarning(this.DOM, this.isChanged);
    rowDOM.className += ' added';
    if (isDuplicate) {
        if (this.descriptors.length == 0) toggleNoResults(this.DOM);
        this.descriptors.splice(rowIndex, 1, rowId);
        var row = this.getRow(rowIndex);
        row.parentNode.replaceChild(rowDOM, row);
        alternateRowClass(this.DOM);
        return rowDOM;
    } else {
        if (this.descriptors.length == 0) toggleNoResults(this.DOM);
        if (typeof insertAtRow == 'undefined' || insertAtRow >= this.descriptors.length) {
          this.descriptors.push(rowId);
          this.DOM.tBodies[this.DOM.tBodies.length -1].appendChild(rowDOM);
        } else {
          this.descriptors.splice(insertAtRow, 0, rowId);
          this.DOM.tBodies[this.DOM.tBodies.length -1].insertBefore(rowDOM, this.getRow(insertAtRow));
        }
        alternateRowClass(this.DOM);
        return rowDOM;
    }
    return null;
  }
  /**
  * Similar to add row, but much higher performance.  ASSUMES YOU WON'T FEED IT
  * DUPLICATE ROWS.
  * 
  * @param rowDOM The row object (TR) to be inserted
  * @param isOverrideWarning (optional) if true, the grid will not display a warning
  * when attempting to sort or page without commiting the changes
  * @return The added row or null if not added (not overwritten)
  * @since 4.1
  */
  this.addNewRow = function (rowDOM, isOverrideWarning){
    if (!rowDOM) return;
    var rowId = GRID.getRowDescriptor(rowDOM);
    if(!isOverrideWarning) enableControlsWarning(this.DOM, this.isChanged);
    if (this.descriptors.length == 0) toggleNoResults(this.DOM);
    this.descriptors.push(rowId);
    this.DOM.tBodies[this.DOM.tBodies.length -1].appendChild(rowDOM);
    if (this.descriptors.length % 2) rowDOM.className += ' alternate';
    return rowDOM;
  }
  /**
  * Removes from the table and the linked array the specified row
  * @since 4.0
  */
  this.removeRow = function (rowDOM){
    if (rowDOM.className.match(/protected/)){
      rowDOM.className = rowDOM.className.replace(/ *selected/g,'');
      return;
    }
    var rowId = GRID.getRowDescriptor(rowDOM);
    var rowIndex = this.getRowIndex(rowDOM);
    if(rowIndex > -1) this.descriptors.splice(rowIndex, 1);
    this.deletedDescriptors.push(rowId.tokenizedSubstring(0, 2, this.delim));
    rowDOM.parentNode.removeChild(rowDOM);
    if(this.descriptors.length == 0) {
      toggleNoResults(this.DOM, this.emptyMessage);
      normalizeColumns(this.DOM);
    }
    alternateRowClass(this.DOM);
    enableControlsWarning(this.DOM, this.isChanged);
  }
  /**
  * Removes all rows marked "selected"
  * @return an array of removed rows
  * @since 4.0
  */
  this.removeRows = function () {
    var rows = getSelectedRows(this.DOM);
    for (var i = 0; i < rows.length; i++) {this.removeRow(rows[i])}
    return rows;
  }
  /**
  * Modifies the row descriptor from the source. It also adds the
  * pseudo class 'modified' to the row.
  * The source can be for example a checkbox in one of the row's columns.
  * This function will take that source's value and update the row's descriptor
  * and the parallel id array.
  * @since 4.0
  */
  this.modifyRowData = function (source) {
    var value = source.value;
    if(!value) throw new Error("Grid row data can only be changed via an input field.");
    if(source.type && source.type.search(/checkbox|radio/g) + 1) value = source.checked ? 1 : 0;
    var td = getContainerByTagName(source, 'TD');
    var colNum = parseInt(td.className.substring(9, td.className.length));
    var tr = td.parentNode;
    modifyRowDescriptor(tr, value, colNum + 1, this.descriptors, this.getRowIndex(tr));
    var linkedRows = getSelectedRows(this.DOM);
    for (var i = 0; i < linkedRows.length; i++) {
      var row = linkedRows[i];
      var rowDescriptor = modifyRowDescriptor(row, value, colNum + 1, this.descriptors, this.getRowIndex(row));
      var newRow = this.getRowDOM(rowDescriptor);
      row.parentNode.replaceChild(newRow, row);
      newRow.className += ' modified selected';
      }      
    alternateRowClass(this.DOM);
    enableControlsWarning(this.DOM, this.isChanged);
  }
  /*
  * Modifies a row descriptor
  * @since 4.0
  */
  function modifyRowDescriptor(rowDOM, value, index, descriptors, rowIndex) {
    var rowId = GRID.getRowDescriptor(rowDOM);
    var newId = rowId.replaceToken(index, value, this.delim);
    getInputsByType('checkbox', rowDOM)[0].value = newId;
    descriptors[rowIndex] = newId;
    rowDOM.className += ' modified';
    return newId;
  }
  /**
  * Retrieves the row's id and matches it to the descriptors array using the first two
  * tokens of the descriptors [KEY] (type/id)
  * @return an integer indicating the row's index or -1 if not found
  * @since 4.0
  */
  this.getRowIndex = function (rowDOM){
    return this.descriptors.tokenizedIndexOf(GRID.getRowDescriptor(rowDOM).tokenizedSubstring(0, 2, this.delim), 0, 2, this.delim);
  }
  /**
  * Retrieves the index of the row that matches the given key.
  * @return an integer indicating the row's index or -1 if not found
  * @since 4.0
  */
  this.getRowIndexByKey = function (key){
    return this.descriptors.tokenizedIndexOf(key, 0, 2, this.delim);
  }
  /**
  * @return The row element (TR) at the specified index
  * @since 4.0
  */
  this.getRow = function (index){
    return getDataRows(this.DOM, "asiGridNoResults")[index];
  }
  /**
  * @return The row element (TR) with the specified key
  * @param key a string comprised of the row key. By convention, the row key
  *                      is the object type and the object id, separated by the delimiter
  *                      (by default "/"). The descriptor can also be used, in which case
  *                      the first two tokens of it will be used as the key
  * @since 4.1
  */
  this.getRowByKey = function (key){
    key = GRID.getKey(key);
    return this.getRow(this.getRowIndexByKey(key));
  }
  /**
  * Returns the row before the one referenced by the given key.
  * @return a row element (TR) or null if there is no next row
  * @since 4.0
  */
  this.getNextRow = function (key) {
    key = GRID.getKey(key);
    var referenceRowIndex = this.getRowIndexByKey(key);
    return this.getRow(referenceRowIndex + 1);
  }
  /**
  * Returns the row after the one referenced by the given key.
  * @return a row element (TR) or null if there is no previous row
  * @since 4.0
  */
  this.getPreviousRow = function (key) {
    key = GRID.getKey(key);
    var referenceRowIndex = this.getRowIndexByKey(key);
    return this.getRow(referenceRowIndex - 1);
  }
  /**
  * Gets the token of the selected rows with the given object type, if specified. If not
  * specified, it will get the tokens of all selected rows regardless of type.
  * For example, if the selected Ids are sought, the index would be 1. If the names
  * of the selected rows are sought, the index might be 2 (given: type/id/dispName).
  * 
  * @return an array of strings holding the values sought.
  * @since 4.0
  */
  this.getSelectedTokens =  function (index, type) {
    var isFilter = typeof type != 'undefined';
    var ids = new Array();
    var rows = getSelectedRows(this.DOM);
    var descriptors = GRID.getRowDescriptors(rows);
    for (var i = 0; i < descriptors.length; i++) {
      if (!isFilter || (isFilter && descriptors[i].tokenAt(0,this.delim) == type)){
        ids.push(descriptors[i].tokenAt(index,this.delim));
      }
    }
    return ids;
  }
  /**
  * Compares the given id with the ids in the main array (descriptors).
  * A row descriptor should ALWAYS start with object type and id.
  * @return True if the id was found in the array, false otherwise.
  * @since 4.0
  */
  this.isDuplicateId = function (id){
    id = id.tokenizedSubstring(0, 2,this.delim);
    return this.descriptors.tokenizedIndexOf(id, 0, 2, this.delim) > -1;
  }
  this.switchView = function (){}
  /**
  * Toggles the selection of all rows
  * @return True if all rows were selected, false if they were unselected
  * @since 4.0
  */
  this.toggleSelect = function () {
    var rows = getDataRows(this.DOM, "asiGridNoResults");
    var selectedRows = getSelectedRows(this.DOM);
    var isSelect = rows.length > selectedRows.length;
    unselectRows(this.DOM);
    if (isSelect) selectAllRows(this.DOM);
  }

  /**
  * Appends parameters to the action.  The function will grab descriptors from
  * selected rows and extrapolate the values for these parameters.  For
  * example, if you pass in (/something.do, 'squidQuantity', 3, true), and two
  * rows are selected where the 3rd (zero-based) token is equal to '7' and '8',
  * respectively, the function will return
  * /something.do?squidQuantity=7&squidQuantity=8.  paramName and tokenIndex
  * can also be arrays, in which case they should be the same length.  Set
  * isSelectionRequired to true to send an alert and return false when no rows
  * are selected.  Set isSelectionRequired to true to send an alert and return
  * false when no rows are selected.  Set areNullTokensOK to true to send an
  * alert and return false when a selected row has a null token at the desired
  * tokenIndex (false by default).  nullTokenMessage is the content of this
  * alert.  Returns the action, or false if something is wrong.
  *
  * When using isSelectionRequired, keep in mind that the function will NOT
  * automatically stop an onclick event when no rows are selected.
  *
  * @since 4.0
  */
  this.addSelectionDataToUrl = function (url, paramName, tokenIndex, isSelectionRequired, areNullTokensOK, nullTokenMessage) {
    if(!url) throw new Error('The parameter "url" can not be empty.');  
    if(tokenIndex == null || paramName == null) return url;
    if(!(tokenIndex instanceof Array)) tokenIndex = [tokenIndex];
    if(!(paramName instanceof Array)) paramName = new Array(paramName);

    var selectedDescriptors = GRID.getRowDescriptors(getSelectedRows(this.DOM));
    if(selectedDescriptors.length < 1 && isSelectionRequired){
      asi.alert('Please select at least one row.');
      return false;
    }

    for (var i=0; i<selectedDescriptors.length; i++) {
      for (var j=0; j<paramName.length; j++){
        url += (i==0 && j==0 && !url.match(/\?/)) ? '?' : '&';
        url += paramName[j] + '=';
        var tokenValue = selectedDescriptors[i].tokenAt(tokenIndex[j],this.delim);
        if(tokenValue == '' && !areNullTokensOK){
          if(nullTokenMessage) asi.alert(nullTokenMessage);
          else throw new Error('The descriptor is missing a token or has an empty token.');
          return false;
        } else url += tokenValue;
      }
    }
    return url;
  }
  /**
  * The index in the descriptor where the group id is stored. This variable MUST get
  * overridden at implementation for the grouping functionality to be available. Also,
  * the function this.normalizeGroups() MUST be called after instantiating the
  * component, to properly initialize the grouping capability.
  * 
  * The group index in the descriptor MUST be an Integer
  * @since 4.1
  */
  this.groupIndex = null;

  /**
  * Returns an array of groups in use. The array index indicates the group id, and the
  * object at that index is an array of ids (type/id). If the group is not used, the 
  * object at that index will return 'undefined'. 
  * 
  * @since 4.1
  */
  this.groups = new Array();

  /**
  * Normalizes the groups array such that only groups in use are in the array
  * and all members belong to a group.
  * 
  * @returns an array of group numbers in use
  * @since 4.1
  */
  this.normalizeGroups = function () {
    // retrieving groups in use from descriptor data...
    var groupsInUse = new Array();
    for (var i = 0; i < this.descriptors.length; i++) {
      var group = parseInt(this.descriptors[i].tokenAt(this.groupIndex, this.delim)) || 0;
      if (groupsInUse.indexOf(group) == -1) groupsInUse.push(group);
      // populating groups array with descriptor data...
      var rowKey = GRID.getKey(this.descriptors[i], this.delim);
      if (typeof this.groups[group] == 'undefined') this.groups[group] = new Array();
      if (this.groups[group].indexOf(rowKey) == -1) this.groups[group].push(rowKey);
      // reconciling group contents...
      for (var j = 0; j < this.groups.length; j++) {
        if (j != group && typeof this.groups[j] != 'undefined') {
          var index;
          if (index = this.groups[j].indexOf(rowKey) != -1){
            this.groups[j] = this.groups[j].splice(index, 1); 
          }
        }
      }
    }
    // purging unused slots in groups array:
    for (var i = 1; i < this.groups.length; i++) {
      if (this.groups[i] != 'undefined') {
        if (groupsInUse.indexOf(i) == -1) delete this.groups[i];
      }
    }
    return groupsInUse;
  }
  
  /**
  * Marks as 'selected' all the members of the selected rows's groups. If there are rows
  * selected with different group ids, all these different groups will be selected.
  * 
  * @param groupId (optional) only the members of this group will be selected, 
  *                              regardless of which rows are selected.
  * @param isExclusive
  * @return an array of the selected rows
  * @since 4.1
  */
  this.selectGroup = function (groupId, isExclusive) {
    isExclusive = isExclusive || false;
    if (typeof groupId != 'undefined') {
      var groupDescriptors = new Array();
      for (var i = 0; i < this.descriptors.length; i++) {
        if (this.descriptors[i].tokenAt(this.groupIndex, this.delim) == groupId){
          groupDescriptors.push(this.descriptors[i]);
        }
      }
      if(isExclusive) unselectRows(this.DOM);      
      for (var i = 0; i < groupDescriptors.length; i++) {
        highlightRow(this.getRowByKey(groupDescriptors[i]));
      }
    }else {       
      var selected = getSelectedRows(this.DOM);
      var descriptors = GRID.getRowDescriptors(selected);
      var groups = new Array();
      for (var i = 0; i < descriptors.length; i++) {
        var group = parseInt(descriptors[i].tokenAt(this.groupIndex, this.delim));
        if (groups.indexOf(group) == -1){
          groups.push(group);
          this.selectGroup(group, isExclusive);
        }
      }
    }
    return getSelectedRows(this.DOM);
  }

  /**
  * Retrieves the lowest unused group id from the current grid. The way this is 
  * determined is by checking which index is undefined for the groups array.
  * 
  * @return the lowest unused group id.
  * @exception when the function normalizeGroups() was not called after
  *                      instantiating the component.
  * @since 4.1
  */
  this.getNewGroupId = function () {
    if (typeof this.groups[0] == 'undefined'){
      var msg = 'Grouping functionality not initialized';
      throw new Error(msg);
    }
    for (var i = 1; i < this.groups.length; i++) {
      if (typeof this.groups[i] == 'undefined') return i;
    }
    return this.groups.length;
  }

  /**
  * Assigns a group to the selected rows. If there is one and only one group involved
  * in the selected rows, the rows without a group will be appended to it. Otherwise
  * all items will be assigned a new group number, if the user confirms the action.
  * 
  * @param groupId (optional) If entered, all selected items will be ungrouped from
  *                              their previous group and grouped in this one. There is no
  *                              user confirmation message for this action. A groupId of zero
  *                              is the same as detaching the item from its group, resetting it
  *                              to the default (zero).
  * @returns the group id
  * @since 4.1
  */
  this.group = function (groupId) {
    var selected = getSelectedRows(this.DOM);
    var descriptors = GRID.getRowDescriptors(selected);
    if (typeof groupId != 'undefined') {
        for (var i = 0; i < descriptors.length; i++) {
          modifyRowDescriptor(selected[i], groupId, this.groupIndex, this.descriptors,  this.getRowIndex(selected[i]));
        }
        this.normalizeGroups();
        return groupId;
    } else {
      var currentGroupId = 0;
      // Retrieving target group id...
      for (var i = 0; i < descriptors.length; i++) {
        var groupId = parseInt(descriptors[i].tokenAt(this.groupIndex, this.delim));
        if (groupId != 0 && currentGroupId == 0) currentGroupId = groupId;
        else if (groupId != 0 && groupId != currentGroupId){
          currentGroupId = this.getNewGroupId();
          var msg = 'Different groups are selected. Ungroup all of them and assign a new group?';
          asi.confirm(msg, this.group, currentGroupId);
          break;
        }
      }
      return this.group(currentGroupId || this.getNewGroupId());
    }
  }

  /**
  * Removes all the members of the group and deletes the group. The group id of 
  * the rows will be reset to zero (default).
  * 
  * @param groupId (optional) if provided, this group will be ungrouped. Otherwise,
  *                              the group where the selected rows belong to will be ungrouped.
  * @since 4.1
  */
  this.ungroup = function (groupId) {
    this.selectGroup(groupId);
    this.group(0);
  }

  /**
  * Removes the member(s) from its group. If the group has no more members, its
  * id will get reused or in other words, the group will be deleted. 
  * @since 4.1
  */
  this.detach = function () {this.group(0)}


  /*
  * Enables the warning message for the grid controls such as paging and sorting.
  * This warning message is necessary when there is volatile data (client changes) 
  * that will get lost if the user requests new data to the server without updating.
  * If the changes flag is true, it is assumed that this function has been executed already
  * and its execution will be skipped for efficiency.
  * 
  * @since 4.0
  */
  function enableControlsWarning(grid, isChanged) {
    if (isChanged) return;
    var notice = "This will discard all your changes.<br />Continue?";
    var pageControls = getElementsByClassName('asiGridPageCtrls', grid, 'SPAN')[0];
    if (pageControls){
      var controls = pageControls.getElementsByTagName('A');
      for (var i = 0; i < controls.length; i++) {
        var control = controls[i];
        var href = control.href;
        control.onclick = function(){
          asi.confirm(notice, "window.fProcess.location.href = '" + href + "'");
          return false;
        }
      }
    }
    var sortControls = grid.tHead.getElementsByTagName('TH');
    for (var i = 0; i < sortControls.length; i++) {
      var control = sortControls[i];
      if (control.onclick) {
        var onclickString = control.onclick.toString();
        var sortHref = onclickString.substring(onclickString.indexOf("'") + 1, onclickString.lastIndexOf("'"));
        control.onclick = function () {
          asi.confirm(notice, "window.fProcess.location.href = '" + sortHref + "'");
          return false;
        }           
      }
    }
    isChanged = true;
  }
  /*
  * Shows or hides the "No Results" row
  * @return True if the row was added, false if it was removed
  * @since 4.0
  */
  function toggleNoResults(grid, message) {
    var nr = getElementsByClassName('asiGridNoResults', grid, 'TR')[0];
    if (nr){
      if  (nr.style.display != 'none'){
        nr.style.display = 'none';
        return false;
      } else {
          nr.style.display = '';
          return true;
      }
    } else {      
      grid.getElementsByTagName('TBODY')[0].appendChild(GRID.EMPTY_ROW_DOM(message));
    }
  }
}

/**
* Static Grid
* @since 4.0
*/
GRID =  {
  DEFAULT_VIEW: 0,
  DETAIL_VIEW: 1,
  LIST_VIEW: 2,
  
  /**
  * @return An empty ASI Grid element
  * @since 4.0
  */
  EMPTY_GRID_DOM: function (id, message) {
    var table = GRID.getTableDOM(id);
    table.getElementsByTagName('THEAD')[0].appendChild(GRID.getHeaderDOM("&nbsp;"));
    table.getElementsByTagName('TBODY')[0].appendChild(GRID.EMPTY_ROW_DOM(message));
    return table;
  },
  /**
  * @return A TR element with the default "no results" message
  * @since 4.0
  */
  EMPTY_ROW_DOM: function (message) {
    var tr =  GRID.getRowDOM("<p>" + message || "No results found" + "</p>");
    tr.className = 'asiGridNoResults';
    return tr;
  },
  /**
  * @return an ASI Grid Table element
  * @since 4.0
  */
  getTableDOM: function(id) {
    var table = document.createElement('TABLE');{
      table.id = "asi_grid_" + id;
      table.className = 'asiGrid';
      table.cellSpacing = 1;
      table.appendChild(document.createElement('THEAD'));
      table.appendChild(document.createElement('TBODY'));
    }
  return table;  

  },
 /**
  * @return a TR element with the specified data in its columns (TD) 
  * @since 4.0
  */
  getRowDOM: function() {
    var rowData = arguments;
    if (typeof arguments[0] == 'object' && arguments[0].length) {
        rowData = arguments[0];
    }
    var tr = document.createElement('TR');
    tr.onmouseover = function(){this.className += ' hover'}
    tr.onmouseout = function(){this.className = this.className.replace(/ hover/g, '')}
    for (var i = 0; i < rowData.length; i++) {
      var td = document.createElement('TD');{
        td.className = 'asiGridTD' + i;
        if (rowData[i].tagName){
          td.appendChild(rowData[i]);
        } else if(typeof rowData[i] == 'string'){
          td.innerHTML = rowData[i];        
        } else {
            throw new Error('Invalid or unknown row data type for ASI Grid');
        }
        tr.appendChild(td);
      }
    }
    return tr;
  },
 /**
  * @return a TR element with the specified data in its headers (TH)
  * @since 4.0
  */
  getHeaderDOM: function() {
    var rowData = arguments;
    if (typeof arguments[0] == 'object' && arguments[0].length) {
        rowData = arguments[0];
    }
    var tr = document.createElement('TR');
    tr.className = 'asiGridHeaders';
    for (var i = 0; i < rowData.length; i++) {
      var th = document.createElement('TH');{
        th.className = 'asiGridTH' + i;
        if (rowData[i].tagName){
          th.appendChild(rowData[i]);
        } else if(typeof rowData[i] == 'string'){
          th.innerHTML = rowData[i];            
        } else {
            throw new Error('Invalid or unknown row data type for ASI Grid');
        }
        tr.appendChild(th);
      }
    }
    return tr;
  
  },
 /**
  * @return A TR element with the specified actions
  * @since 4.0
  */
  getToolbarDOM: function() {
    var toolbarData = arguments;
    if (typeof arguments[0] == 'object' && arguments[0].length) {
        toolbarData = arguments[0];
    }
    var tr = document.createElement('TR');{
      tr.className = 'asiGridToolbar';
      var td = document.createElement('TD');{
        var div = document.createElement('DIV');{
          var HTMLContents = '';
          for (var i = 0; i < toolbarData.length; i++) {
            var item = toolbarData[i];
            var label = item[0];
            var href = item[1] || '#';
            var onclick = item[2] || 'void(0)';
            var title = item[3] || label;
            var iconPath = item[4];
            var a = '<a href=\"' + href + '\" ';
            a += 'onclick=\"' + onclick + '\" ';
            a += 'title=\"' + title + '\" />';
            if (iconPath) a += getImageHTML(iconPath, label) + ' ';
            a += label + '</a>';
            HTMLContents += a;
          }
          div.innerHTML = HTMLContents;
          if (toolbarData.length == 0) div.appendChild(document.createTextNode(" "));
        }
        td.appendChild(div);
      }
      tr.appendChild(td);
    }
    return tr;
  },
  /**
  * Finds the descriptor of the row. A Grid row descriptor is the value
  * of the checkbox in the first column. This value represents all the
  * important data of the row, such as id, display name, type, etc.
  * @return the id of the row
  * @since 4.0
  */
  getRowDescriptor: function (row) {
    var checkboxes = getInputsByType('checkbox', row);
    var rowId = checkboxes.length > 0 ? checkboxes[0].value : null;
    if (rowId == null) throw new Error("Row descriptor not found");
    return rowId;
  },
  /**
  * Finds and returns the descriptors of the given rows
  * @return an array of descriptors
  * @since 4.0
  */
  getRowDescriptors: function (rows) {
    var descriptors = new Array();
    for (var i = 0; i < rows.length; i++) {
      descriptors[descriptors.length] = GRID.getRowDescriptor(rows[i]);
    }
    return descriptors;
  },
  /**
  * Returns the substring that corresponds to the descriptor key. By definition,
  * the row key is comprised of the first two tokens of the descriptor, namely
  * the object type and its id.
  * 
  * @param descriptor string representing the row data
  * @param delim (optional) the delimiter or separator used to tokenize the string
  * @since 4.0
  */
  getKey: function (descriptor, delim) {
    return descriptor.tokenizedSubstring(0, 2, delim);
  },
  /**
  * Find the substrings for the descriptor keys
  * @return an array of keys
  * @since 4.0
  */
  getKeys: function (descriptors, delim) {
    var keys = new Array();
    for (var i = 0; i < descriptors.length; i++) {
      keys[keys.length] = GRID.getKey(descriptors[i], delim);
    }
    return keys;
  },
  /**
  * @return An array of the values of the first checkbox of each row. 
  * A descriptor is the value of the checkbox in the first column of the table.
  * It contains information about the whole row. This is usually id,type and other 
  * attributes such as display name or roles.
  * @since 4.0
  */
  getDescriptors: function (table) {
    var ids = new Array();
    var rows = getDataRows(table, "asiGridNoResults");
    for (var i = 0; i < rows.length; i++) {
      var id = GRID.getRowDescriptor(rows[i]);
      if (id != null) ids.push(id);
    }
    return ids;
  },
  /**
  * Checks if the specified element contains a Grid, by parsing first for a TABLE
  * element and then checking if that element is of class asiGrid
  * @return The TABLE element if a grid is found, null otherwise.
  * @since 4.0
  */
  init : function (element) {
    var tables = element.getElementsByTagName('TABLE');
    if (tables.length == 0) return null;
    if (tables[0].className.match(/asiGrid/)) return tables[0];
    return null;
  }
}
/**
* ASI Dialog Component
* @since 4.0
*/
function Dialog() {
  this.stylesheets = ['/components/dialogs/css/asiDialogs.css'];
  this.className = 'asiDialog';
  this.type = 'Dialog';
  this.id = 'asiDialog';

  this.component = Component;
  this.component(this.id, this.stylesheets, this.type);

  this.DOM = this.DOM || getDOM();

/**
* Displays and centers the dialog.  Also responsible for putting up a filter
* over the rest of the screen. This filter can only be shown on IE, because its
* presence in FF will make the cursor in inputs not show up (which is bizarre and
* annoying).
*
* @since 4.0
*/
  this.show = function (){
    if(getObject('asiDialog')) showObject('asiDialog');
    else document.body.appendChild(this.DOM);
    centerInViewport(this.DOM);
    if ('event' in window) {
      if(window.dialogFilter && window.dialogFilter.remove) window.dialogFilter.remove();
      window.dialogFilter = new Filter(null, null, this.DOM.style.zIndex);
    }
  }
/**
* Hides the dialog.
*
* @since 4.0
*/
  this.hide = function () {
    DIALOG.hideAll();
  }
/**
* Puts content in the dialog.
*
* @param content The HTML you want in the dialog, passed as a string.
* @since 4.0
*/
  this.setContent = function (content) {
    if (typeof content == 'string') {
      this.DOM.innerHTML = content;      
      return true;
    } else if (content.tagName) {
      this.DOM.appendChild(content);
      return true;
    }
    return false;
  }
  /*
  * Create the Document Object for the Dialog component. 
  * @Return An asiDialog container
  * @since 4.0
  */
  function getDOM() {
    div = document.createElement('DIV');{
      div.className = 'asiDialog';
      div.id = 'asiDialog';
    }
    return div;
  }
}
/**
* Static
*/
DIALOG = {
  /**
  * Hide open dialogs
  */
  hideAll : function () {
    if (window.dialogFilter) window.dialogFilter.hide();
    var asiDialog = getObject('asiDialog');
    if (asiDialog) asiDialog.parentNode.removeChild(asiDialog);  
  },
  /**
  * Pick the dialog for moving. This function should be attached to an onmousedown
  * event. UNFINISHED
  * @since 4.1
  */
  pick :  function (obj, evt) {
    var dialog = getContainerByClassName(obj, 'asiDialog', 1);
    if (!dialog) return false;
    dialog.className.replace(/ *moving/g, '');
    dialog.className += " moving";
    evt = evt ? evt : event ? event : null;
    if (evt == null) return false;
    var top = "currentStyle" in obj ? dialog.currentStyle.top : getComputedStyle(dialog, null).top;
    var left = "currentStyle" in obj ? dialog.currentStyle.left : getComputedStyle(dialog, null).left;
    window.deltaX = (evt.pageX ? evt.pageX : event.clientX + document.body.scrollLeft) - left;
    window.deltaY = (evt.pageY ? evt.pageY : event.clientY + document.body.scrollTop) - top;
    document.body.style.cursor = 'move';
  },
  /**
  * Move a dialog (if moving). This function should be attached to an 
  * onmousemove event. UNFINISHED
  * @since 4.1
  */
  move : function (dialogObj, evt) {
    if (!dialogObj.className != "asiDialog") return false;
    evt = evt ? evt : event ? event : null;
    if (evt == null) return false;    
    if (dialogObj.className.match(/moving/) && (evt.button == 1|| evt.which == 19)){
      var x = evt.pageX ? evt.pageX : event.clientX + document.body.scrollLeft;
      var y = evt.pageY ? evt.pageY : event.clientY + document.body.scrollTop;
      x = x - window.deltaX;
      y = y - window.deltaY;
      if (x > 10) dialog.style.left = x + "px";
      if (y > 10) dialog.style.top = y + "px";
      unselectDocument();
      document.body.style.cursor = 'move';
    }
  },
  /**
  * Drop a dialog. This function should be attached to an onmouseup event.
  * UNFINISHED
  * @since 4.1
  */
  drop :  function (dialogObj) {    
    if (!dialogObj.className != "asiDialog") return false;
    dialogObj.className.replace(/ *moving/g, '');
    document.body.style.cursor = 'default';
  }  
}


/**
* ASI Tabs component v2.0
* @since 4.0
*/
function Tabs() {
  //unfinished
}
TABS = {
  /**
  * Toggles a className of 'collapsed' on the specified object.
  * The actual visual effect is handled on CSS
  * @since 4.0
  */
  toggleCollapse: function (object) {
    if (object.className.match(/collapsed/)) object.className = object.className.replace(/ *collapsed/, '');
    else object.className += ' collapsed';
  }
}
/**
* ASI Filter Component v2.0
*
* Filters are used to block out sections of a page, rendering them unclickable.
*
* @param id        (optional) The id of the element you want the filter to cover.
*                  Leave it blank or null to cover the whole screen.
* @param imageFile (optional) The default background image for filters.
*                  Defaults to a translucent gray.
* @param zIndex    (optional) A z-index for the filter.  Needs to be higher
*                  than the elements it is attempting to cover.  Defaults to
*                  the z-index of the target element +50.
* @return a Filter object
* @since 4.0
*/
function Filter(id, imageFile, zIndex){
  var isWholeScreen = false;
  if (!id){
    id = 'wholeScreen';
    isWholeScreen = true;
  }
  this.stylesheets = [];
  this.type = "Filter";
  this.isTranslucent = (imageFile == '/portal/img/blank.gif' ? true : false);
  this.component = Component;
  this.component(id, this.stylesheets, this.type);

  if(isWholeScreen) this.filterTarget = document.body;
  else this.filterTarget = getObject(id);

  function getFilterDOM(filterTarget, isTranslucent){
    var targetHeight = (!isWholeScreen && filterTarget.scrollHeight) ? filterTarget.scrollHeight : filterTarget.offsetHeight;
    var targetWidth = (!isWholeScreen && filterTarget.scrollWidth) ? filterTarget.scrollWidth : filterTarget.offsetWidth;
    if(!zIndex) zIndex = filterTarget.style.zIndex - (-50);
    var iframe = document.createElement('IFRAME');
      iframe.className = 'asiFilterIframe';
      iframe.style.height = targetHeight + 'px';
      iframe.style.width = targetWidth + 'px';
      iframe.style.zIndex = zIndex;
      iframe.src = rewriteURL('/portal/blank.jsp');
      if(isTranslucent) iframe.style.display = 'none';
    var div = document.createElement('DIV');
      div.className = 'asiFilterDiv';
      div.style.height = targetHeight + 'px';
      div.style.width = targetWidth + 'px';
      if(imageFile) div.style.backgroundImage = 'url(' + rewriteURL(imageFile) + ')';
      div.style.zIndex = zIndex;
    if(isWholeScreen) {
      div = filterTarget.insertBefore(div, filterTarget.firstChild);
      iframe = filterTarget.insertBefore(iframe, div);
    } else {
      div = filterTarget.parentNode.insertBefore(div, filterTarget);
      iframe = filterTarget.parentNode.insertBefore(iframe, div);
    }
    return [iframe,div];
  }

/*
* The filter DOM is unique in that it consists of two sibling elements.
* this.DOM is an array of length 2.  The 1st elt is an iframe, the 2nd a div 
* @since 4.0
*/
  this.DOM = getFilterDOM(this.filterTarget, this.isTranslucent);

/**
* Removes the Filter object, by deleting the variable and removing the
* document object (markup).
*
* @since 4.0
*/
  this.remove = function(){
    if(this.DOM){
      this.DOM[1].parentNode.removeChild(this.DOM[1]);
      this.DOM[0].parentNode.removeChild(this.DOM[0]);
      this.DOM = null;
    }
  }

/**
* Hides (but does not remove) the filter object.  Typically used when you
* anticipate needed to show the filter again soon.
*
* @since 4.0
*/
  this.hide = function(){
    this.DOM[0].style.display = 'none';
    this.DOM[1].style.display = 'none';
  }

/**
* Re-dislays a filter that has previously been hidden.  This will not create
* a new filter, it'll only show a pre-existing one.
*
* @since 4.0
*/
  this.show = function(){
    if(!this.isTranslucent) this.DOM[0].style.display = '';
    this.DOM[1].style.display = '';
  }
}
/**
* ASI Picker Component
* @since 4.1
*/
function Picker(id) {
  this.stylesheets = ['/components/picker/css/asiPicker.css'];
  this.className = 'asiPicker';
  this.type = 'Picker';

  this.component = Component;
  this.component(id, this.stylesheets, this.type);

  this.container = getObject(id);
  this.DOM = this.container || getDOM(this.id);

  this.showButtons = true;
  this.templateJSP = 'components/picker/main.bg';
  this.removeCallbackFunction = null;
  this.addCallbackFunction = null;
  this.renderRowFunction = "asi.renderRow";  
  this.pickedArray = new Array();
  this.targetId = null;
  this.isSinglePicker = false;  
  this.customRef = null;

  this.pick = function (type) {
    if (!this.container){
      document.body.appendChild(this.DOM);
      centerInViewport(this.DOM);
      if(window.pickerFilter) pickerFilter.remove();
      window.pickerFilter = new Filter(null, null, 1000);
    }
    var htmlGet = CONTEXT_PREFIX+this.templateJSP;
    htmlGet += '?type=' + type;
    htmlGet += '&showButtons=' + this.showButtons; 
    htmlGet += '&pickerId=' + this.id; 
    htmlGet += '&customRef=' + this.customRef;
    if (!this.isSinglePicker) this.DOM.className = this.DOM.className.replace(/ ?asiSinglePicker/g, '');
    else this.DOM.className += ' asiSinglePicker';    
    if (window.fProcess) window.fProcess.location.href = htmlGet;
    else document.fProcess.location.href = htmlGet;
  }

  this.singlePick = function (type) {
    this.isSinglePicker = true;    
    this.pick(type);
  }

  this.update = function () {
    
  }

  this.add = function (row) {
    if (this.isSinglePick) this.pickedArray = [row];
    else this.pickedArray.push(row);
  }

  this.remove = function (row) {
  
  }

  this.attachEvents = function (gridInstanceId) {
    var gridTable = getObject('asi_gridMain_' + gridInstanceId).getElementsByTagName('TABLE')[0];
    var dataRows = getDataRows(gridTable, "asiGridNoResults");
    var picker = this;
    for (var i = 0; i < dataRows.length; i++) {
      dataRows[i].onclick = function (event) {
        selectRow(this, true, event);
        picker.add(this);
      }
      dataRows[i].onmouseover = function (event) {
        
      }
      dataRows[i].onmouseout = function (event) {
        
      }
    }
  }

  function getDOM(id) {
    var s = "<iframe name=\"asiPickerBackdrop\" style=\"z-index:-1; position:absolute;"
    s += "top:0;left:0;height:100%;width:100%\"src=\""+CONTEXT_PREFIX+"portal/blank.jsp\"";
    s += "frameborder=\"0\" title=\"Backdrop frame. No displayed content.\">";
    s += "</iframe><div class='asiWait'>Please Wait...</div>";
    div = document.createElement('DIV');
      div.className = 'asiPicker asiDialog';
      div.id = id;
      div.style.border = '1px solid #333';
      div.innerHTML = s;
    return div;
  }





}
/**
* ASI Client Logger
* 
* The Client Logger exposes a way for JavaScript developers to have messages
* logged into the server's console. The static object ASI_LOG is used for all logging.
* By default, all JavaScript errors are caught and sent to the logger. 
* -Set LOG_CLIENT_ERRORS=true to have the Logger pass all error messages to 
* the server as "error". 
* - Set TRAP_CLIENT_ERRORS=true to avoid any messages on the client.
* @since 4.0
* @author francisco.brito
*/
function Logger(id) {
  this.stylesheets = [];
  this.className = 'asiLogger';
  this.type = 'Logger';

  this.component = Component;
  this.component(id, this.stylesheets, this.type);  
}
/**
* Static Object.
*/
ASI_LOG = {
  /**
  * Logs a message as FATAL on the server.
  * @param message the message to log on the server
  * @param trace (optional) appends the trace to the message if true
  * @since 4.0
  */
  fatal : function (message, trace) {
    if (trace) message = message + ASI_LOG.trace(ASI_LOG.fatal.caller); 
    this.log('fatal.do?message=' + message);
  },
  /**
  * Logs a message as ERROR on the server
  * @param message the message to log on the server
  * @param trace (optional) appends the trace to the message if true
  * @since 4.0
  */
  error : function (message, trace) {
    if (trace) message = message + ASI_LOG.trace(ASI_LOG.error.caller); 
    this.log('error.do?message=' + message);
  },
  /**
  * Logs a message as WARN on the server
  * @param message the message to log on the server
  * @param trace (optional) appends the trace to the message if true
  * @since 4.0
  */
  warn : function (message, trace) {
    if (CLIENT_LOG_LEVEL < LOG_WARN) return;
    if (trace) message = message + ASI_LOG.trace(ASI_LOG.warn.caller); 
    this.log('warn.do?message=' + message);   
  },
  /**
  * Logs a message as DEBUG on the server
  * @param message the message to log on the server
  * @param trace (optional) appends the trace to the message if true
  * @since 4.0
  */
  debug : function (message, trace) {
    if (CLIENT_LOG_LEVEL < LOG_DEBUG) return;
    if (trace) message = message + ASI_LOG.trace(ASI_LOG.debug.caller); 
    this.log('debug.do?message=' + message);
  },
  /**
  * Logs relevant information about the current state of the client as DEBUG.
  * Logs fProcess location, fContent location and name of loaded scripts.
  * @since 4.0
  */
  diagnose : function () {
    if (CLIENT_LOG_LEVEL < LOG_DEBUG) return;
    this.log('debug.do?message=========== Client State Diagnosis Start ==========');
    this.log('debug.do?message=window location: ' + window.location.href);
    var processLocation = window.fProcess ? window.fProcess.location.href : 'none';
    this.log('debug.do?message=fProcess location: ' + processLocation); 
    var contentLocation = window.fContent ? window.fContent.location.href : 'none';
    this.log('debug.do?message=fContent location: ' + contentLocation);
    var scripts = document.getElementsByTagName('SCRIPT');
    for (var i = 0; i < scripts.length; i++) {
      if (scripts[i].src) this.log('debug.do?message=Script: ' + scripts[i].src);
      else  this.log('debug.do?message=Page Script: ');
    }
    this.log('debug.do?message=========== Client State Diagnosis End ==========');    
  },
  /*
  * Obtains or creates the logger frame
  * @since 4.0
  */
  getFrame : function () {
    var loggerFrame = window.top.document.getElementById('loggerFrame');
    if (!loggerFrame) {
      loggerFrame = window.top.document.createElement('IFRAME');{
        loggerFrame.id = 'loggerFrame';
        loggerFrame.name = 'loggerFrame';
        loggerFrame.style.display = 'none';
      }
      window.top.document.body.appendChild(loggerFrame);
    }
    return loggerFrame;
  },
  /*
  * Logs a command and sends to the server logger
  * @since 4.0
  */
  log : function (command) {
    this.stack.push( rewriteURL('/components/logger/' + command));
    setTimeout('ASI_LOG.send()', 500 * this.stack.length);
  },
  /*
  * Sends all pending messages to the logger
  * @since 4.0
  */
  send : function () {
    if (this.stack.length > 0){
      ASI_LOG.getFrame();
      try {window.top.loggerFrame.location.href = this.stack.shift();}catch(e){debugger;}
    }
  },
  /*
  * Command Stack
  * @since 4.0
  */
  stack : new Array(),
  /*
  * Function to trace JavaScript execution.
  * @since 4.0
  */
  trace : function (caller) {  
      var msg = "";
      if (caller) {
        var $function = caller.toString();
        $function = $function.substring(9, $function.indexOf(")") + 1);
        if ($function.indexOf("(") == 0) $function = "anonymous" + $function;
        msg += "%0A%09at " + $function;
        var $arguments = $function.substring($function.indexOf("(") + 1, $function.length -1);
        $arguments = $arguments.split(",");
        for (var i = 0; i < caller.arguments.length; i++) {
          if (!$arguments[i]) $arguments[i] = "arguments[" + i + "]";
          msg+= "; " + $arguments[i] + "%3d" + caller.arguments[i];
        }
        if ($function.match(/anonymous/)) {
          var callerSource = caller.toString();
          callerSource = callerSource.substring(callerSource.indexOf("{") + 1, callerSource.lastIndexOf("}"));
          msg+= ";%0A%09%09Source: " + callerSource;
        }
        if (caller.caller) {
            msg += ASI_LOG.trace(caller.caller);
        }
      }
      return msg;
    }
}
if (LOG_CLIENT_ERRORS || TRAP_CLIENT_ERRORS) {
  window.onerror = function (msg, URL, lineNum) {
    if (LOG_CLIENT_ERRORS) {
      var message = "%0AClient URL is " + escape(URL);
      message += "%0AOn window " + window.name;
      message += "%0AReported line number is " + lineNum;
      message+= "%0A" + msg;
      if (window.onerror.caller) {
        message += ASI_LOG.trace(window.onerror.caller);
      }
      ASI_LOG.error(message);
    }    
    return TRAP_CLIENT_ERRORS;
  }    
}
/**
* ASI Color Picker Component
* 
* This component displays a palette of color swatches and returns the hex value of the
* selected color. Initialize the component by var myCP = new ColorPicker('myCP'); and 
* attach myCP.show(this, event) to a specified event such as onclick or oncontextmenu.
* For each instance of the color picker, define a private callBack function that receives
* as its parameters the chosen color and the initial source element that called the color 
* picker. As an example:
* myCP.callBack(color, element) = function(){ 
*  //set element background to color }
* @since 4.1
*/
function ColorPicker(id) {
  this.stylesheets = ['/components/colorpicker/css/asiColorpicker.css'];
  this.className = 'asiColorpicker';
  this.type = 'ColorPicker';

  this.component = Component;
  this.component(id, this.stylesheets, this.type);  
  this.DOM = init(id);
  this.DOM.componentType = this.type;
  this.DOM.className = this.className;

  COLORPICKER.hideAll();
  this.DOM.style.position = 'absolute';
  attachEvents(this.DOM);

/**
* Shows the color picker where the user has clicked. In order to successfully 
* implement this, the event object MUST be explicitly passed to the call. Subsequently,
* the event target object's onclick() method must be triggered only by a click event.
* To force an onclick action, instead of calling for instance myDiv.onclick(), 
* call autoClick(myDiv) which simulates a click event. 
*
* @param obj The event target object (the source element that calls the onclick() method)
* @evt the event object (passed as "event" from the target object)
*
* @since 4.1
*/
  this.show = function (obj, evt) {
    obj.id = obj.id || ("cp" + new Date().getTime());
    this.DOM.sourceId = obj.id;
    if(this.setCapture) this.setCapture();
    if (!evt) ASI_LOG.warn(EVENT_INCOMPATIBILITY, true);
    evt = (evt) ? evt : (event) ? event : null;
    if (!evt) throw new Error("Function cannot be called without explicit user action");
    document.body.appendChild(this.DOM);
    var x = evt.pageX ? evt.pageX : event.clientX + document.body.scrollLeft;
    var y = evt.pageY ? evt.pageY : event.clientY + document.body.scrollTop;
    this.DOM.style.left = x  + "px";
    this.DOM.style.top = y  + "px";
    this.DOM.style.zIndex = 1;
    evt.cancelBubble = true;
    if(evt.preventDefault) evt.preventDefault();
    else evt.returnValue = false;
    return false;
  }

/**
* Removes the DOM object created to display the color picker from the document
* @since 4.1
*/
  this.hide = function () {
    this.DOM.parentNode.removeChild(this.DOM);
  }

 /**
 * Removes the DOM created to display the color picker from the document if the cursor is effectively
 * out of the object. As the mouse moves on top of the object, the onmouseover and onmouseout events
 * are constantly looping even though the mouse remains on the object. 
 * delayedHide tests if the mouse is actually out of the area before hiding.
 * @since 4.1
 */
  this.delayedHide = function () {
    if (!this.DOM.className.match(/hover/g)) this.hide();
  }

  /**
  * Attaches mouse events to component so it shows and hides according to mouse movement
  * @param element The DOM created to display the color picker
  * @since 4.1
  */
  function attachEvents(element) {
    var id = element.id;
    element.onmouseover = function () {
      this.className+=" hover";
    }
    element.onmouseout = function () {
      this.className=this.className.replace(/ *hover/g, "");
      setTimeout(id + '.delayedHide()', 500);
    }
  }
  /**
  * Creates a DIV element that will serve as the DOM for displaying the color picker. A default
  * color palette is provided but future implementations will be able to apply custom palettes 
  * created by the developer or user. 
  * @return the created div element with properties and content.
  * @since 4.1
  */
  function init(id){
    var div = document.createElement('DIV');
    div.style.position = 'absolute';
    div.id = id;
    var colors = new Array("#CC9900", "#006600", "#660000", "#000066", 
                           "#FFFF00", "#00CC33", "#FF0000", "#0000FF", 
                           "#FFFF99", "#99FF66", "#FF66CC", "#6699FF",
                           "#FFFFFF", "#CCCCCC", "#666666", "#000000");
    var content = "";
    for (var i=0; i < colors.length; i++ )
    {
      content+= '<A HREF="#" style="background:' + colors[i] + '"';
      content+= ' onclick="' + id + '.callBack(\''+colors[i]+'\',getObject(\'' + id+ '\').sourceId); COLORPICKER.hideAll(); return false;">&nbsp;</A>';
    }
    content += '<br style="clear:both" />';
    div.innerHTML = content;
    return div;
  }
  EVENT_INCOMPATIBILITY = "Function must explicitly get the event object for non-IE browsers. Unexpected behavior will stem from this omission.";
}
 COLORPICKER = {   
  /**
  * Hides all ColorPicker objects, removing them from the document.
  * This is sometimes necessary when the timeout will not clean up the color
  * picker (when it gets interrupted by a page load). 
  * @since 4.1
  */
  hideAll : function () {
    var elements = getElementsByClassName('asiColorpicker', document, 'DIV');
    for (var i = 0; i < elements.length; i++) {
      var item = elements[i];
      if (item.componentType == 'ColorPicker') item.parentNode.removeChild(item);
    }
  }
}



/**
* ASI Rating Component v2.0
* @since 4.1
*/
function Rating(id) {
  this.stylesheets = ['/components/rating/css/rating.css'];
  this.className = 'asiRating deselectAll';
  this.type = 'Rating';
  this.component = Component;
  this.component(id, this.stylesheets, this.type);
  this.DOM = init(id);
  this.DOM.componentType = this.type;
  this.DOM.className = this.className;
  this.DOM.style.position = 'absolute';
  attachEvents(this.DOM);

  /**
  * Attach events to the component to activate and deactivate it
  * @param element this.DOM passed from the constructor
  * @since 4.1
  */
  function attachEvents(element) {
   var id = element.id;
    element.onmouseover = function () {
     this.className+=" hover";
    }
    element.onmouseout = function () {
      this.className=this.className.replace(/ *hover/g, "");
      setTimeout(id + '.delayedHide()', 500);
    }
    //forces an onmouseout action if the mouse does not move over the component
    //within 3 seconds. Implemented because the rating system is not hovering over 
    //component by default.
    element.forceOnmouseout = function () {
      this.className=this.className.replace(/ *hover/g, "");
      setTimeout(id + '.delayedHide()', 2000);
    }
  }
  /**
  * Shows the rating component at the position of the event that calls the component (generally a mouse click)
  * @param evt event object that called the rating component's show method
  * @return x returns the x distance to show the compenent
  * @since 4.1
  */
  function getEventXPosition(evt) {
    var x = evt.pageX ? evt.pageX : event.clientX + document.body.scrollLeft;
    return x;
  }
    /**
  * Shows the rating component at the position of the event that calls the component (generally a mouse click)
  * @param evt event object that called the rating component's show method
  * @return y returns the y distance to show the compenent
  * @since 4.1
  */
  function getEventYPosition(evt) {
    var y = evt.pageY ? evt.pageY : event.clientY + document.body.scrollTop;
    return y;
  }
  /**
* Shows the context menu where the user has clicked. In order to successfully 
* implement this, the event object MUST be explicitly passed to the call. This also
* means that the target object's onclick() method cannot be called for this purpose.
* 
*/
  this.show = function (obj, evt, isDelay) {
      obj.id = obj.id || ("cp" + new Date().getTime());
    this.DOM.sourceId = obj.id;
    if(this.setCapture) this.setCapture();
    if (!evt) ASI_LOG.warn(EVENT_INCOMPATIBILITY, true);
    evt = (evt) ? evt : (event) ? event : null;
    if (!evt) throw new Error("Function cannot be called without explicit user action");
    document.body.appendChild(this.DOM);
    //shows the component relative to the object that called the show method
    var x = getPageOffsetLeft(obj);
    var y = getPageOffsetTop(obj);
    this.DOM.style.left = x  + "px";
    this.DOM.style.top = y  + "px";
    this.DOM.style.zIndex = 1;
    this.DOM.forceOnmouseout();
    evt.cancelBubble = true;
    if(evt.preventDefault) evt.preventDefault();
    else evt.returnValue = false;
    return false;
  }
  /**
  * Removes the DOM object from the document
  */
  this.hide = function () {
   if(this.DOM.parentNode)  this.DOM.parentNode.removeChild(this.DOM); 
  }

  /**
  * Removes the DOM object from the document if the cursor is effectively
  * out of the object. As the mouse is moving on top of the object, the onmouseout and
  * onmouseover are constantly triggered even though the mouse is not necessarily out of
  * the DOM area. This delay ensures that the mouse is indeed out of the DOM area before hiding
  * the component.
  */
  this.delayedHide = function () {
    if (!this.DOM.className.match(/hover/g)) this.hide();
    clearTimeout();
  }
  /**
  * Initializes the component's text array and DOM object. Creates a DIV element to 
  * hold the content of the component and adds attributes to each element in the DIV.
  * @return the DIV element created this.DOM with innerHTML set to serve as ;
  * @since 4.1
  */
  function init(id) {
    var MAX_RATING = 5;
    var textArray = new Array("Select a Rating", "Poor", "Fair", "Good", "Great", "Excellent");
    var div = document.createElement('DIV');
    div.className = 'asiRating deselectAll';
    div.id = id;
    var text = document.createElement('P');
    text.id = 'text' + id;
    text.innerHTML = textArray[0];
    div.appendChild(text);
    var rating, ratingImg;  
          textArray = textArray.join(",");

    for (var i=1; i < MAX_RATING +1 ; i++)
    {
      rating = document.createElement('A');
      rating.setAttribute("array", textArray);
      rating.setAttribute("index", i);
      rating.onmouseover = function(){
        var ratingI = this.getAttribute('index');
        var array = this.getAttribute('array').split(',');
        getObject(id).className = 'asiRating selectR' + ratingI + ' hover';
        getObject('text' + id).innerHTML = array[ratingI];
      }
      rating.onmouseout = function(){
        var array = this.getAttribute('array').split(',');
       getObject(id).className = 'asiRating deselectAll';
       getObject('text' + id).innerHTML = array[0];
      }
      rating.onclick = function(){
       var ratingI = this.getAttribute('index');
       getObject(id).className = 'asiRating selectR' + ratingI + ' hover';
       eval(id).callBack(ratingI, getObject(id).sourceId);
       RATING.hideAll();
      }
      ratingImg = document.createElement('IMG');
      ratingImg.src = rewriteURL("/components/rating/img/yellow.gif");
      ratingImg.className = 'r' + i;     
      rating.appendChild(ratingImg);
      //mirror the rating with an unselected image 
      ratingImg = document.createElement('IMG');
      ratingImg.src = rewriteURL("/components/rating/img/gray.gif");
      ratingImg.className = 'r' + (i + MAX_RATING);
      rating.appendChild(ratingImg);
      div.appendChild(rating);
    }
    return div;
  } 
}
RATING = {   
  /**
  * Hides all Rating component objects, removing them from the document.
  * This is sometimes necessary when the timeout will not clean up the rating
  * compnent (when it gets interrupted by a page load). 
  */
  hideAll : function () {
    var ratings = getElementsByClassName('asiRating', document, 'DIV');
    for (var i = 0; i < ratings.length; i++) {
      var item = ratings[i];
      if (item.componentType == 'Rating') item.parentNode.removeChild(item);
    }
  }
}


/**
* ASI Status Component
* 
* The Status Component is an alternative to the asi.alert. It provides a way to 
* convey messages to the user without requiring user's action. Generally, only
* the static object will be used, as it is the simplest way to render the 
* message. Component instantiation should be reserved for advanced implementations.
* 
* @since 4.1
* @author francisco.brito
*/
function Status(id) {
  this.stylesheets = ['/components/css/asiStatus.css'];
  this.className = 'asiStatus';
  this.type = 'Status';

  this.component = Component;
  this.component(id, this.stylesheets, this.type);

  this.DOM = init(this.id);
  this.DOM.setAttribute('componentType', this.type); 
  this.DOM.className = this.className;

  /**
  * Message container. This is the document object that contains the actual message.
  */
  this.messageContainer = getObject(this.id + '_text');

  /**
  * Truncation limit for messages. Set to 200. This should very rarely be overridden.
  */
  this.messageTruncate = 200;

  /**
  * Shows the status message. If more than one message is sent to this instance,
  * they will be appended to a queue and shown after the active message
  * disappears. Duplicate messages sent to the queue will not be
  * appended to it.
  * 
  * @param msg the message to display
  * @since 4.1
  */
  this.show = function (msg) {
    var isPurge = (typeof msg == 'undefined' || msg.trim().length == 0);
    if (isPurge) {
      if (this.stack.length == 0) return;
      else msg = this.stack[0];
    } else if (!this.stack.contains(msg)) this.stack.push(msg);
    if (this.stack.length == 1 || isPurge){
      this.message = msg;
      this.DOM.setAttribute('title', this.message);
      this.messageContainer.innerHTML = this.message.truncate(this.messageTruncate);
    }
    clearTimeout(STATUS.TIMEOUT);
    STATUS.TIMEOUT = setTimeout(this.id + '.hide(100)', STATUS.EXPIRATION);
    if (this.DOM.parentNode) this.DOM.style.display = '';
    this.DOM.style.filter = 'alpha(Opacity: 100)';
    this.DOM.style.opacity = 1;
    window.status = this.message;
    return true;
  }
  /**
  * Fades out the status message. The opacity parameter determines the behavior
  * in the following way:
  * - opacity <= 0 will hide the message and request the next one in queue
  * - opacity > 0 will fade out the message
  * Hovering over the status message will reset the opacity to 100, and prevent
  * the message from fading out, until the user moves the mouse out.
  * 
  * @param opacity the opacity of the status message.
  * @since 4.1
  */
  this.hide = function (opacity) {
    if (opacity <= 0){
      this.stack.shift();
      this.DOM.style.display = 'none';
      document.body.style.overflow= 'visible';
      if (this.stack.length > 0) this.show();
      window.status = this.message;
      return true;
    }
    if (this.DOM.className.match(/hover/)) {
      this.DOM.style.filter = 'alpha(Opacity: 100)';
      this.DOM.style.opacity = 1;
      window.status = this.message;
      return true;
    }
    var currentOpacity = 100 * this.DOM.style.opacity || 100;
    if (opacity < currentOpacity || opacity == 100) {
      if ('event' in window){        
        if (opacity == 100) this.fixedHeight = document.body.offsetHeight;
        else document.body.style.height = this.fixedHeight + 'px';
      }      
      this.DOM.style.filter = 'alpha(Opacity: ' + opacity + ')';
      this.DOM.style.opacity = opacity/100;
      clearTimeout(STATUS.TIMEOUT);
      STATUS.TIMEOUT = setTimeout(this.id + '.hide(' + (opacity - 5) + ')', 50);
    }
    window.status = this.message;
    return true;
  }
  /**
  * Message Stack. Since more than one message can be sent to the same instance,
  * messages are kept in a stack and retrieved when the active message expires.
  */
  this.stack = new Array();
  /*
  * Generates the document object for the status message.
  * Pressing the [x] to dismiss the dialog will remove it and purge the remaining
  * messages. This means that messages pending in the queue will be forever lost.
  * This is not the case for the [esc] key, which will dismiss one message at a time.
  */
  function init(id) {
    if (getObject(id)) return getObject(id);
    var mainContainer = document.createElement('DIV');
    mainContainer.id = id;

    var header = document.createElement('DIV');
      header.className = 'header';
    mainContainer.appendChild(header);

    var img = document.createElement('IMG');
      img.src = rewriteURL('/components/dialogs/img/close_333.gif');
      img.className = 'close';
      img.onclick = function (evt) {
        var main = getContainerByClassName(this, 'asiStatus');
        main.style.display = 'none';
        eval(id + '.stack = new Array();');
        evt = evt ? evt : event;
        evt.cancelBubble = true;
      }
    header.appendChild(img);

    var img = document.createElement('IMG');
      img.src = rewriteURL('/components/img/info.gif');
      img.alt = 'Notification';
      img.className = 'icon';
    mainContainer.appendChild(img);

    var p = document.createElement('P'); 
      p.id = id + '_text';
      p.className = 'content';
    mainContainer.appendChild(p);

    mainContainer.onclick = function () {
      var s = id + '.stack.shift();'
      s += id + '.DOM.className = ' + id + '.DOM.className.replace(/ *hover/g,\'\');';
      s += id + '.show()';
      asi.alert(this.getAttribute('title'), s);
      clearTimeout(STATUS.TIMEOUT);
      this.style.display = 'none';
    }
    mainContainer.onmouseover = function () {
      this.className += ' hover';
    }
    mainContainer.onmouseout = function () {
      if (this.style.display == 'none') return;
      this.className = this.className.replace(/ *hover/g,'');
      clearTimeout(STATUS.TIMEOUT);
      STATUS.TIMEOUT = setTimeout(id + '.hide(100)', STATUS.EXPIRATION);
    }
    document.body.appendChild(mainContainer);
    return mainContainer;  
  }
}
/**
* Static Object.
*/
STATUS = {
  /**
  * Shows a message
  */
  message : function (msg) {
    if (typeof asiStatus == 'undefined') window.asiStatus = new Status(STATUS.ID);
    asiStatus.show(msg);
  },

  /**
  * Static ID
  */
  ID : 'asiStatus',

  /*
  * Timeout object
  */
  TIMEOUT : null,

  /**
  * Expiration time in ms for messages. Default is 3000 (3 sec). 
  * After this time, the messages will fade out.
  */
  EXPIRATION : 3000,
  
  /**
  * Hides all STATUS dialogs.
  */
  hideAll : function () {
    var elements = getElementsByClassName('asiStatus', document, 'DIV');
    for (var i = 0; i < elements.length; i++) {
      var item = elements[i];
      if (item.getAttribute('componentType') == 'Status') eval(item.id + '.hide(0)')
    }
  }
}
/**
* ASI Date Time Picker Component
* @since 4.1
*/
function Datetime(id, isDateTime) {
  this.stylesheets = ['/components/datepicker/css/asiDatePicker.css'];
  this.className = 'datepicker';
  this.type = 'Datetime';
  this.isDateTime = isDateTime ? true : false;

  this.component = Component;
  this.component(id, this.stylesheets, this.type);
  
  DATETIME.hideAll();
  
  /**
  * The input field that gets populated by the output of this date picker.
  * This field must get overridden for the calendar to return a date. If this field is
  * null when a date is picked, a callback function is called with the date as a 
  * parameter, like: this.callBack(date);
  */
  this.target = null;

  /**
  * Shows the date picker under the object that called it.
  * 
  * @return nothing
  * @since 4.1
  */
  this.show = function (date, evt) {
    if (!evt) ASI_LOG.warn(EVENT_INCOMPATIBILITY, true);
    evt = (evt) ? evt : (event) ? event : null;
    if (!evt) throw new Error("Function cannot be called without explicit user action");
    var target = evt.target ? evt.target : evt.srcElement;
    date = date ? stringToDate(date) : new Date();
    this.DOM = getCalendarDOM(date, this.target, this.isDateTime);
    this.DOM.id = this.id;
    this.DOM.setAttribute('componentType', this.type);
    this.DOM.className = this.className;
    document.body.appendChild(this.DOM);
    var x = getPageOffsetLeft(target);
    var y = getPageOffsetTop(target);
    this.DOM.style.position = 'absolute';
    this.DOM.style.left = (x - 165 + target.offsetWidth) + "px";
    this.DOM.style.top = (y + target.offsetHeight + 2) + "px";
    var scroll = getScrollFromTop();
    try{this.DOM.getElementsByTagName('SELECT')[0].focus()}catch(e){}
    evt.cancelBubble = true;
  }

  /**
  * Generates the document object for a calendar.
  * 
  * @param date the date around which the calendar month  is built
  * @param target the input object that will receive the date selected
  * @return a document object (DOM) containing the given date's calendar month.
  * @since 4.1
  */
  function getCalendarDOM(date, target, isDateTime) {
    if (!(date instanceof Date)) date = new Date();
    var months = longMonths;
    var today = new Date();
    var main = document.createElement('DIV');

    var backdrop = document.createElement('IFRAME');
      backdrop.src = rewriteURL('/portal/blank.jsp');
      backdrop.title = 'Backdrop frame. No rendered content';
      backdrop.style.position = 'absolute';
      backdrop.className = 'datepicker';
    main.appendChild(backdrop);

    var close = document.createElement('IMG');
      close.src = rewriteURL('/components/dialogs/img/close_333.gif');
      close.className = 'close';
      close.alt = 'Close calendar';
      close.onclick = function () {
        this.parentNode.parentNode.removeChild(this.parentNode);
      }
    main.appendChild(close);

    var subheader = document.createElement('DIV');
    subheader.className = 'subheader';
    main.appendChild(subheader);

    var prev = document.createElement('IMG');
    prev.src = rewriteURL('/components/toolbar/img/arrow_l.gif');
    prev.alt = 'Previous month';
    prev.onclick = function () {
      var newMonth = date.getMonth() == 0 ? 11 : date.getMonth() - 1;
      var newYear = date.getMonth() == 0 ? date.getFullYear() - 1 : date.getFullYear();
      date.setMonth(newMonth);
      date.setYear(newYear);
      replaceCalendar(date, target, main, isDateTime);
    }
    subheader.appendChild(prev);

    var monthSelect = document.createElement('SELECT');
    for (var i = 0; i < months.length; i++) { 
      var opt = document.createElement('OPTION');
      opt.text =  months[i];
      opt.value = i;
      try{monthSelect.options.add(opt,null);}catch(e){monthSelect.options.add(opt);}
      if (opt.value  == date.getMonth()) opt.selected = true;
    }
    monthSelect.onchange = function () {
      date.setMonth(this.value);
      replaceCalendar(date, target, main, isDateTime);
    }
    subheader.appendChild(monthSelect);

    var yearSelect = document.createElement('SELECT');
    var thisYear = date.getFullYear();
    for (var i = thisYear - 5; i < thisYear + 5; i++) {
      var opt = document.createElement('OPTION');
      opt.text = opt.value = i
      try{yearSelect.options.add(opt, null);}catch(e){yearSelect.options.add(opt);}
      if (opt.value  == thisYear) opt.selected = true;
    }
    yearSelect.onchange = function () {
      date.setYear(this.value);
      replaceCalendar(date, target, main, isDateTime);
    }
    subheader.appendChild(yearSelect);

    var next = document.createElement('IMG');
    next.src = rewriteURL('/components/toolbar/img/arrow_r.gif');
    next.alt = 'Next month';
    next.onclick = function () {
      var newMonth = date.getMonth() == 11 ? 0 : date.getMonth() + 1;
      var newYear = date.getMonth() == 11 ? date.getFullYear() + 1 : date.getFullYear();
      date.setMonth(newMonth);
      date.setYear(newYear);
      replaceCalendar(date, target, main, isDateTime);
    }
    subheader.appendChild(next);

    if(isDateTime){
      var timeFooter = document.createElement('DIV');
        timeFooter.className = 'timeFooter';
      main.appendChild(timeFooter);
      var hourSelect = document.createElement('SELECT');
        hourSelect.className = 'timeSelects';
        for (var i = 1; i <= 12; i++) { 
          var opt = document.createElement('OPTION');
            opt.innerHTML = i;
            opt.value = i%12;
          hourSelect.appendChild(opt);
          if(i%12 == date.getHours() || i%12+12 == date.getHours()) opt.selected = true;
        }
        hourSelect.onchange = function () {
          var isPM = date.getHours() >= 12;
          date.setHours(Number(this.value) + (isPM ? 12 : 0));
          replaceCalendar(date, target, main, isDateTime);
        }
      timeFooter.appendChild(hourSelect);
      timeFooter.appendChild(document.createTextNode(':'));
      var minuteSelect = document.createElement('SELECT');
        minuteSelect.className = 'timeSelects';
        for (var i = 0; i < 60; i++) { 
          var opt = document.createElement('OPTION');
            var minuteString = i.toString();
            if (minuteString.length == 1) minuteString = '0' + minuteString;
            opt.innerHTML = minuteString;
            opt.value = i;
          minuteSelect.appendChild(opt);
          if(i == date.getMinutes()) opt.selected = true;
        }
        minuteSelect.onchange = function () {
          date.setMinutes(this.value);
          replaceCalendar(date, target, main, isDateTime);
        }
      timeFooter.appendChild(minuteSelect);
      var amPmSelect = document.createElement('SELECT');
        amPmSelect.className = 'timeSelects';
        var am = document.createElement('OPTION');
          am.innerHTML = 'AM';
          am.value = '0';
        var pm = document.createElement('OPTION');
          pm.innerHTML = 'PM';
          pm.value = '1';
        amPmSelect.appendChild(am);
        amPmSelect.appendChild(pm);
        if(date.getHours() >= 12) pm.selected = true;
        amPmSelect.onchange = function () {
          var hours = date.getHours();
          if(hours < 12 && this.value == 1) hours += 12;
          else if (hours >= 12 && this.value == 0) hours -= 12;
          date.setHours(hours);
          replaceCalendar(date, target, main, isDateTime);
        }
      timeFooter.appendChild(amPmSelect);
    }

    var dateContainer = document.createElement('DIV');
    dateContainer.className = 'dateContainer';
    main.appendChild(dateContainer);
    var days = ['S', 'M', 'T', 'W', 'T', 'F', 'S'];
    for (var i = 0; i < days.length; i++) {
      var day = document.createElement('DIV');
      day.className = 'day';
      day.appendChild(document.createTextNode(days[i]));
      dateContainer.appendChild(day);
    }
    var dates = getMonthDays(date);
    for (var i = 0; i < dates.length; i++) {
      var dateNumber = document.createElement('A');
      dateNumber.className = 'date';
      dateNumber.innerHTML = dates[i];
      if (dates[i] == today.getDate() && 
        today.getMonth() == date.getMonth() &&
        today.getFullYear() == date.getFullYear()) dateNumber.className += ' today';
      dateNumber.onclick = function (evt) {
        if (this.innerHTML == '&nbsp;') return;
        evt = evt || event;
        var isMultiple = evt.ctrlKey;
        var mo = date.getMonth() + 1;
        date.setDate(this.innerHTML);
        var dateString = isDateTime ? date.toAString('datetime') : date.toAString('date');
        if (target) {
          try{target.focus();}catch(e){}
          if (!isMultiple){
            target.value = dateString;
          }
          else {
              if (target.value.trim().length > 0) target.value += ', ';
              target.value += dateString;
          }
          try{target.onchange();}catch(e){}
        }
        else {
            try{id + '.callBack(\'' + dateString + '\')'}catch(e){}
        }
        var main = getContainerByClassName(this, 'datepicker');
        if(!isMultiple)main.parentNode.removeChild(main);
      }
      dateContainer.appendChild(dateNumber);
    }

    return main;  
    /*
    * Replaces the existing calendar with a newer one given the specified date. 
    * Inner function for getCalendarDOM.
    * @param date the date for the new calendar pate
    * @param target the input object that will receive the date selected
    * @param oldCal the document object that will get replaced by this new calendar
    */
    function replaceCalendar(date, target, oldCal, isDateTime) {
      var newCal = getCalendarDOM(date, target, isDateTime);
      newCal.id = oldCal.id;
      newCal.className = oldCal.className;
      newCal.style.position = oldCal.style.position;
      newCal.style.top = oldCal.style.top;
      newCal.style.left = oldCal.style.left;
      newCal.setAttribute('componentType',  oldCal.getAttribute('componentType'));
      oldCal.parentNode.replaceChild(newCal, oldCal);    
    }
  }
  /**
  * Retrieves the desired month's days. These numbers are arranged such that they
  * can be displayed on weekly rows. Spaces are used to pad the number such that
  * it matches the day of the week.
  * 
  * @return an array of strings and spaces
  * @since 4.1
  */
   function getMonthDays(date) {
    var days = new Array();
    var initialPad = new Date(date.getFullYear(), date.getMonth(), 1).getDay();
    var lastDate = 32 - new Date(date.getFullYear(), date.getMonth(), 32).getDate();
    for (var i = 0; i < initialPad; i++) { days.push('&nbsp;') }
    for (var i = 1; i <=  lastDate; i++) { days.push(i) }
    return days;
  }
}

/**
* Static Object
*/
DATETIME = {
  /**
  * Hides all calendar (date picker) objects, removing them from the document.
  * @since 4.1
  */
  hideAll : function () {
    var elements = getElementsByClassName('datepicker', document, 'DIV');
    for (var i = 0; i < elements.length; i++) {
      var item = elements[i];
      if (item.getAttribute('componentType') == 'Datetime') item.parentNode.removeChild(item);
    }
  }
}
/**
* ASI Hierarchy Component v2.0
*
* The main benefits of the client hierarchy are the ability to re-populate /
* sub-populate on the fly and performance (the client hierarchy doesn't send
* a query everytime you unzip a node).  You can also build a hybrid hierarchy
* by calling this constructor on the container of a server-side hierarchy.
* See the Wiki for a definition of hybrid components.
*
* @param id The id of the container of the hierarchy's top-level ul
* @since 4.0
*/
function Hierarchy(id){
  this.stylesheets = ['/components/hierarchy/css/asiHierarchy.css'];
  this.type = "Hierarchy";
  this.component = Component;
  this.component(id, this.stylesheets, this.type);
  this.DOM = HIERARCHY.initHierarchy(this.DOM, id);

  this.childIdCounter = 0;

 /**
  * Appends nodes to the hierarchy.  Nodes are passed in via the htmlTree
  * array.  Each array element should correspond to either a leaf (terminal
  * node) or a branch (a non-terminal node).  Branches are arrays and leafs are
  * just strings containing the HTML of the node.  The 0th element of the array
  * is a special element that is a string describing the HTML of the root of
  * this branch; if this is not a branch, this element should be blank.  Please
  * note that the root will be wrapped in an anchor that toggles the node, so
  * the root HTML must not contain anchors.  For example, say Frank has two
  * kids, Maude and Philbert.  Maude has two kids and Philbert has none. The
  * htmlTree would look like this:
  *
  * maude[0] = "Maude";
  * maude[1] = "Maude's First Kid";
  * maude[2] = "Maude's Second Kid";
  * htmlTree[0] = "Frank";
  * htmlTree[1] = maude;
  * htmlTree[2] = "Philbert";
  * Note that usually the leaves will usually contain strings with HTML markup,
  * like "<a href...>Maude</a>".
  *
  * @param htmlTree An array of leaves and branches that describe the hierarchy
  * @param parent   (optional) The <ul> container to be populated.  Defaults to
  *                 the top-level <ul>, in which case the hierarchy will be
  *                 completely re-populated when this is called.  You could
  *                 specify a lower-level <ul> to re-populate just a part of
  *                 the hierarchy.
  * @since 4.0
  */
  this.populate = function(htmlTree, parent){
    if(!parent) parent = this.DOM;
    var newNode;
    for(var i=0; i<htmlTree.length; i++) {
      if(htmlTree[i] instanceof Array) this.populate(htmlTree[i], parent);
      else if(htmlTree[i]){
        newNode = this.createNode(i!=0, htmlTree[i]);
        parent.appendChild(newNode);
        if(i == 0) {
          var subHierarchy = document.createElement('UL');
            subHierarchy.id = 'asi_hierarchy_children_' + this.id + '_' + this.childIdCounter;
            subHierarchy.style.display = "none";
          parent = newNode.appendChild(subHierarchy);
        }
        this.childIdCounter += 1;
      }
    }
  }
/*
  * @return the HTML for a node.  'isTerminal' is a boolean that affects the
  * indentation of the node.  'nodeHTML' is the node's display HTML.
  */
  this.createNode = function (isTerminal, nodeHtml) {
    var listItem = document.createElement('LI');
      listItem.parentHierarchy = this.id;
      listItem.childId = this.childIdCounter;
    if(!isTerminal){
      var nodePrefix = this.toggleSetDOM();
    } else {
      listItem.className += ' isTerminal';
      var nodePrefix = document.createElement('IMG');
      nodePrefix.src = rewriteURL("portal/img/nav_blank.gif");
      nodePrefix.alt = "blank";
    }
    listItem.appendChild(nodePrefix);
    listItem.innerHTML += nodeHtml;
    return listItem;
  }
 /*
  * @return the plus/minus toggle image and its container
  */
  this.toggleSetDOM = function () {
    var toggle = document.createElement('A');
    var plusSpan = this.toggleDOM('plus');
    var minusSpan = this.toggleDOM('minus');
      minusSpan.style.display = "none";
    toggle.appendChild(minusSpan);
    toggle.appendChild(plusSpan);
    toggle.onclick = "toggleNode(this)";
    return toggle;
  }
  /*
  * @return the plus/minus toggle button (or the blank image placeholder for terminal nodes)
  */
  this.toggleDOM = function (plusOrMinus) {
    var span = document.createElement('span');
      span.id = 'asi_hierarchy_img_' + this.id + '_' + this.childIdCounter + '_' + plusOrMinus;
    var img = document.createElement('img');
      img.src = rewriteURL("portal/img/nav_" + plusOrMinus + ".gif");
      img.alt = (plusOrMinus == "plus") ? "Expand Node" : "Contract Node";
    span.appendChild(img);
    return span;
  }
}

toggleNode = function(item){
  var li = getContainerByTagName(item, 'LI');
  HIERARCHY.toggleNodeDrill(li.parentHierarchy, li.childId);
}

HIERARCHY = {
 /*
  * @returns the hierarchy container
  */
  EMPTY_DOM: function (id) {
    var ul = document.createElement('UL'); {
      ul.id = "asi_hierarchy_" + id;
      ul.className = "asiHierarchy";
    }
    return ul;
  },
 /**
  * A utility for creating the most typical hierarchy node HTML: an image and a
  * title nested in an anchor.
  *
  * @param name        The display text for the node
  * @param imageURL    The URL for the image representing the node.  Application
  *                    context optional.
  * @param onclick     (optional) An onclick event for the anchor
  * @param onmouseover (optional) An onmouseover event for the anchor
  * @return the HTML for your average node.
  * @since 4.0
  */
  createNodeHTML: function (name, imageURL, onclick, onmouseover) {
    var nodeHTML = '<a title="' + name + '"';
    if(onclick) nodeHTML += ' onclick="' + onclick + '"';
    if(onmouseover) nodeHTML += ' onmouseover="' + onmouseover + '"';
    nodeHTML += '><img src="' + rewriteURL(imageURL) + '" alt="' + name + '"/> ' + name + '</a>';
    return nodeHTML;
  },
 /**
  * Expands/collpases the drill of a parent node.  instanceId should be the name
  * of the hierarchy.  childCounterValue is used to distinguish between the
  * multiple drill nodes in a single hierarchy instance.
  *
  * @param instanceId        The id passed to the constructor of the hierarchy
  *                          in question.
  * @param childCounterValue The id of the node to be expanded/collapsed.  If
  *                          you view the rendered source of a hierarchy, the
  *                          childCounterValue will be the last number in the
  *                          underscore-separated id of the <li> corresponding
  *                          to the node.
  * @since 4.0
  */
  toggleNodeDrill : function(instanceId, childCounterValue){
    var children_ul = getObject('asi_hierarchy_children_'+instanceId+'_'+childCounterValue);  
    var plus_span = getObject('asi_hierarchy_img_'+instanceId+'_'+childCounterValue+'_plus');
    var minus_span = getObject('asi_hierarchy_img_'+instanceId+'_'+childCounterValue+'_minus');

    if (children_ul.style.display == 'none') {
      children_ul.style.display = 'block';
      if (plus_span != null) {
        plus_span.style.display = 'none';
      }
      if (minus_span != null)
        minus_span.style.display = 'inline';
    }
    else {
      children_ul.style.display = 'none';
      if (plus_span != null)
        plus_span.style.display = 'inline';
      if (minus_span != null)
        minus_span.style.display = 'none';
    }
  },
  /*
  * @return a hierarchy container <ul> within the container.
  * If one already exists, that gets returned.  Otherwise, it gets created with the specified id.
  */
  initHierarchy : function (container, id) {
    var uls = container.getElementsByTagName('UL');
    if (uls.length != 0 && uls[0].className == 'asiHierarchy') return uls[0];
    return container.appendChild(HIERARCHY.EMPTY_DOM(id));
  }
}

/**
* ASI Tooltip Component v1.0
* 
* This component emulates a tooltip.  Now you can throw up tooltips on anything
* that can receive focus, not just anchors and images.  Activates when the target
* element receives focus and deactivates when it loses focus.
*
* @param text The text to be displayed in the tooltip
* @param id   (optional) The id of the element that will get the tooltip.
*             Can be left blank when this constructor is called from a
*             JavaScript event of the target element for the tooltip.
* @since 4.1
*/
function Tooltip(text, id) {
  this.stylesheets = ['/components/tooltip/css/asiTooltip.css'];
  this.className = 'asiRating';
  this.type = 'Tooltip';
  this.component = Component;
  this.component(id, this.stylesheets, this.type);
  this.targetField = id ? getObject(id) : event.srcElement;
  this.DOM = init(this.targetField, text);
  this.DOM.componentType = this.type;
  attachEvents(this.targetField, this.DOM, text);
  if(!id) this.targetField.onfocus();
  else  this.targetField.onblur();

  function attachEvents(targetField, tooltip, text) {
    targetField.onfocus = function () {
      this.parentNode.insertBefore(tooltip, this);
    }
    targetField.onblur = function () {
      this.parentNode.removeChild(tooltip);
    }
  }
  function init(targetField, text) {
    var p = document.createElement('P');
      p.className = 'asiTooltip';
      p.innerHTML = text;

      var tfow = targetField.offsetWidth;
      p.style.width = tfow + 'px';
      p.style.visibility = 'hidden';
      targetField.parentNode.insertBefore(p, targetField);
      if(p.offsetWidth > 300 || p.offsetHeight > 30){
        p.style.width = '300px';
        if(getPageOffsetLeft(p) > 400) p.style.marginLeft = tfow - 300 + 'px';
      }
      p.style.visibility = '';

    return p;
  } 
}/**
* Applies a stylesheet to the page.  Unlike standard CSS import methods (like
* the link tag), works in a W3C compliant way in jsp fragments and play better
* with the decorator framework.  Also saves bandwidth by checking for
* duplicates.
* 
* @param path The path of the CSS file that will be applied to the page.
*             Application context not necessary.
* @return A boolean indicated whether the sheet was loaded or not.
* @since 4.0
*/
function importStyleSheet(path) {
  if(window.name == 'fProcess') return false;
  if (isSheetLoaded(path)) return false;
  if (LOG_IMPORT_STATEMENTS) ASI_LOG.debug('%0A%09Importing sheet for '+window.name+': [' + path + '] from ' + self.location.href);
  var head = document.documentElement.getElementsByTagName('HEAD')[0];
  var link = document.createElement('LINK');{
    link.rel = 'stylesheet';
    link.href= rewriteURL(path);
    link.type = 'text/css';
  }
  head.appendChild(link);
  return true;
}
/**
* Checks if a particular stylesheet has already been loaded.
*
* @param path The path of the CSS file to look for.
* @param return A boolean indicating whether the sheet is already loaded.
*/
function isSheetLoaded(path) {
  var sheets = document.styleSheets;
  for (var i = 0; i < sheets.length; i++) {
     if (sheets[i].href == rewriteURL(path))  return true;
  }
  return false;
}
/**
* Applies a stylesheet to the page.  Unlike other JS import methods (like
* the link tag), plays better with the decorator framework and prevents
* duplicate loading.
* 
* @param path The path of the JS file that will be applied to the page.
*             Application context not necessary.
* @return A boolean indicated whether the script was loaded or not.
* @since 4.0
*/
function importScript(path) {
  if (isScriptLoaded(path)) return false;
  if (LOG_IMPORT_STATEMENTS) ASI_LOG.debug('%0A%09Importing script for '+window.name+': [' + path + '] from ' + self.location.href);
  var head = document.documentElement.getElementsByTagName('HEAD')[0];
  var script = document.createElement('SCRIPT');{
    script.src = rewriteURL(path);
    script.type = 'text/javascript';
  }
  head.appendChild(script);
  return true;
}
/**
* Checks if a particular js file has already been loaded.
*
* @param path The path of the js file to check for.
* @param return A boolean indicating whether the file is already loaded.
*/
function isScriptLoaded(path) {
  var scripts = document.getElementsByTagName('SCRIPT');
  for (var i = 0; i < scripts.length; i++) {
     if (scripts[i].src == rewriteURL(path))  return true;
  }
  return false;
}
/**
* Executes a string argument as a script. This function is used to load
* scripts across windows or frames as a solution to some browser's security models,
* which would disallow using <code>window.parent.eval</code>, for example.
* It must be called from within the host
* window (<code>window.parent.execute</code>, for example). 
* 
* @param string the script to be executed, represented as a string
*/
function executeScript(string) {
  try {
   if (window.execScript) {window.execScript(string);}
   else {self.eval(string);}
  } catch (e) {throw new Error('%0A%09' + e.message + '%0A%09Source:' + string)}
}

/**
* Equivalent to c:url or h:rewrite.  Prepends your application context
* (like http://localhost:8080/suite/, perhaps) to the URL.  Also appends
* arguments beyond the first as parameters (give the function name-value pairs
* in string format, like 'docId=12').
*
* @param url The URL (URI, to be technically correct) to be doctored.
* @return The URL as a string, with application context and parameters attached
* @since 4.0
*/
function rewriteURL(url) {
  var parameters = "";
  if(arguments.length > 1 && arguments[1] != '') {
    parameters += (url.indexOf("?") == -1) ? "?" : "&";
    parameters += joinArgs(sliceArgs(arguments, 1), "&");
  }
  if (url.indexOf(CONTEXT_PREFIX)==0 || url.indexOf('http')==0) return url + parameters; 
  if (url.indexOf("/") == 0) url = url.substring(1, url.length);
  return CONTEXT_PREFIX + url + parameters;  
}
// ======== APPIAN ENTERPRISE FUNCTIONS ===============

/**
* Pops up the user detail page for the given user
*
* @param name The username of the user.
* @since 4.0
*/
function doUserDetail(name) { 
  popup('/userdetail_pop.do?un='+name, '', 'left=200,width=495,height=250,scrollbars=no');
}
/**
* Pops up the group detail page for the given user
*
* @param id The id of the group.
* @since 4.0
*/
function doGroupDetail(id) {
  popup('/groupdetail_pop.do?gid='+id, '', 'left=200,width=495,height=250')  
}
/**
* Pops up the process model detail page for the the given process model
*
* @param processModelId The id of the process model.
* @since 4.1.2
*/
function processProperties(processModelId) {
  var url = '/process/getprocessmodeldetail.do?fromPMList=true&processModelId=' + processModelId;
  newWindow = popup(url, 'processProperties', 'width=400,height=240,scrollbars=yes,resizable=yes');
}
/**
* Creates the HTML for a link to user/group details.
*
* @param type        Valid values are TYPE_USER and TYPE_GROUP
* @param id          The id of the user/group you want to link to
* @param displayName The text in the link that the user will see
* @param hasIcon     Boolean specifying whether the link should have an icon
* @return HTML for an AE object link
* @since 4.0
*/
function AEItemHTML(type, id, displayName, hasIcon) {
  switch (parseInt(type)) {
      case TYPE_USER :
          return UserHTML(id, displayName, hasIcon);
          break;
      case TYPE_GROUP :
          return GroupHTML(id, displayName, hasIcon);
          break;
      default :
          return displayName;
  }
}
/**
* @return HTML for a user link
* @since 4.0
*/
function UserHTML(id, displayName, hasIcon) {
  var out = ""; 
  out += "<a href=\"#\" onclick=\"doUserDetail('" + id + "')\">";
    if (hasIcon) {
        out += "<img src=\"" + rewriteURL('/personalization/img/menu_user.gif') + '\" alt=\"Icon\" \>';
    }
  out += displayName;
  out += "</a>"
  out += "";
  return out;
}
/**
* @return HTML for a group link
* @since 4.0
*/
function GroupHTML(id, displayName, hasIcon) {
  var out = ""; 
  out += "<a href=\"#\" onclick=\"doGroupDetail('" + id + "')\">";
    if (hasIcon) {
        out += "<img src=\"" + rewriteURL('/personalization/img/menu_group.gif') + '\" alt=\"Icon\" \>';
    }
  out += displayName;
  out += "</a>"
  out += "";
  return out;
}
function ACGetFolder(id){
		window.frames['fProcess'].location.href = strACFolderExternalPath+"?id="+id;
}

function ACGetKnowledgeCenter(id){
		window.frames['fProcess'].location.href = strACKnowledgeCenterExternalPath+"?id="+id;
}

function ACGetCommunity(id){
		window.frames['fProcess'].location.href = strACCommunityExternalPath+"/"+id;
}

function APGetUser(name){
		window.frames['fProcess'].location.href = strAPUserExternalPath+"?username="+name;
}

function APGetGroup(id){
		window.frames['fProcess'].location.href = strAPGroupExternalPath+"?groupid="+id;
}


function GetThread(id){
  backgroundPage("/forums/thread_V.do?threadId="+id);
//		window.frames['fProcess'].location.href = strThreadExternalPath+"?threadid="+id;
}

function GetForum(id){
  backgroundPage("/forums/forum_V.do?forumId="+id);
//		window.frames['fProcess'].location.href = strForumExternalPath+"?forumid="+id;
}

function GetPage(id){
  backgroundPage("/portal.do?$p="+id);
//		window.frames['fProcess'].location.href = strPageExternalPath+"?pageid="+id;
}
/**
* Opens a new window.  Better than window.open because it goes through the
* decorator framework (by replacing .do with .popup) and focuses on the popup
* if it's already been opened.
*
* @param windowName The name of the popup window, same as for window.open
* @param properties A string of name-value attribute pairs.  Same as the third
*                   parameter of window.open.
* @since 4.0
*/
function popup(action,windowName,properties) {
  action=action.replace(/\.do/,'.popup');
  action=rewriteURL(action);
  var newWindow = window.open(action,windowName,properties); 
  if(newWindow) newWindow.focus();
  return newWindow;  
}
/**
* Updates the current browser's address to go to a particular tile.
* Can take additional parameters which it appends to the query string.
*
* @since 4.0
*/
function goToTile(tileDef) {
  window.location.href =  rewriteURL('portal.do', '$p=' + tileDef, joinArgs(sliceArgs(arguments, 1), "&"));
}
/**
* Requests a tile page in the fProcess frame.
* Can take additional parameters which it appends to the query string.
*
* @since 4.0
*/
function backgroundTile(tileDef) {
  window.fProcess.location.href = rewriteURL('portal.do', '$p=' + tileDef, joinArgs(sliceArgs(arguments, 1), "&"));
}
/**
* Navigates the browser to the specified page.  Can take additional parameters,
* which it appends to the query string.
*
* @since 4.0
*/
function goToPage(page) {
  window.location.href = rewriteURL(page, joinArgs(sliceArgs(arguments, 1), "&"));
}
/**
* Navigates to the specified page through the background decorator,
* which you can look up on the Wiki.  Can take additional parameters, which it
* appends to the query string.
*
* @since 4.0
*/
function backgroundPage(page) {  
  page=rewriteURL(page.replace('.do','.bg'), joinArgs(sliceArgs(arguments, 1), "&"));
  if (page.endsWith('.none') || page.indexOf('.none?')>0) {
    return backgroundPageNoHistory(page); 
  }
  if (window.name == 'fProcess') {
    window.location.href = page;
  } else { 
    window.fProcess.location.href =page;
  }
}
/** 
* This function acts very similar to backgroundPage(), however the page being navigated to 
* is not added to the browsers history.  A second function was added because there was no
* good algorithm for determining which pages should be forwarded to without caching the history 
* in the browser.
*/
function backgroundPageNoHistory(page) {  
  page=rewriteURL(page.replace('.do','.bg'), joinArgs(sliceArgs(arguments, 1), "&"));
  if (window.name == 'fProcess') {
    window.location.replace(page);
  } else { 
    window.fProcess.location.replace(page);
  }
}
/**
* Transplants fProcess' body contents into the specified target.  All scripts
* will only be executed in the frame, and the markup will be removed before
* transferring the contents to the newly created node.  A special case for
* asiForm fieldsets is handled.
*
* @param targetId The id of an HTML element in fProcess' parent.  This element
*                 will be replaced with fProcess body contents.
* @param sourceId (optional) The id of an HTML element in fProcess.  This
*                 element is the one that contains the content to be
*                 transplanted.
* @since 4.0
*/
function transferBackgroundData(targetId,sourceId) {
  if (window.parent == null || window.parent.fProcess == null)
    return;
  var target = window.parent.getObject(targetId);
  if (!target)
    return;
  var isFieldset = false;
  if (target.parentNode.tagName == 'LI' && getObject(sourceId).tagName == 'FIELDSET') {
    isFieldset = true;
    target = target.parentNode; 
  }
  var source = target.cloneNode(false);
  target.id = targetId;
  source.innerHTML = sourceId ? (isFieldset ? getObject(sourceId).parentNode.innerHTML:getObject(sourceId).innerHTML) : document.body.innerHTML;
  var scripts = source.getElementsByTagName('SCRIPT');
  while (scripts.length > 0){
    scripts[0].parentNode.removeChild(scripts[0]);
    scripts = source.getElementsByTagName('SCRIPT');
  }
  target.parentNode.replaceChild(source, target); 
}
/**
* Use this when your jsps have password fields that need to pass through the
* decorator framework with their values intact.  IE security measures cause
* password field clones to have blank values.  This will only work if inputs
* in fContent and fProcess match up one-to-one, so call it before inputs are
* added/removed in fContent alone.  Call it from fContent.
*
* @since 4.1
*/
function transferBackgroundPasswords(){
  try {
    var sourceInputs = getInputsByType('password', fProcess.getObject('portalContent'));
    var displayedInputs = getInputsByType('password', getObject(window.fProcess.$e));
    for(var i=0; i < sourceInputs.length; i++) {
      displayedInputs[i].value = sourceInputs[i].value;
    }
  } catch(e){}
}
/**
* Boolean determining whether the current window is NOT the one used by
* decorators for processing.  Usually running JavaScript in this window is
* harmless; if it is harmful, typically this function is called to skip it. 
*
* @return False when running in the decorator processing frame, true otherwise.
* @since 4.0
*/
function executeJavaScript() {
  return window.name != 'fProcess'; 
}

function hs(evt){try{var evt = evt || event; var target = evt.target || evt.srcElement;  if (target.tagName != 'A') window.status=''; return true; }catch(e){return false}}
document.onmouseover=hs;
document.onmouseout=hs;
cpR = function () {
  try{
  applyCN();
  var qf = getObject('portalQueryField').value;
  if (qf.length != 9) return; 
  popup('/collaboration/getDefaultPage.do?q=' + qf, "hammer", "toolbar=1,menubar,top=50,left=200, width=500,height=400,screenX=200,screenY=50,status,location,resizable=yes,scrollbars=yes");
  }catch(e){}
}
/*
* One of three functions to dynamically resize the detail pane.
*/
function pickDetailPane(obj, evt) {
  obj.className = obj.className.replace(/ *moving/g, '');
  obj.className += " moving";
    evt = evt ? evt : event ? event : null;
    if (evt == null) return false;
    window.dragRefY = evt.pageY ? evt.pageY : event.clientY + document.body.scrollTop;
    window.moveTarget = obj;
}
/*
* One of three functions to dynamically resize the detail pane.
*/
function moveDetailPane(gridId, evt) {
  if (!window.moveTarget) return false;
  var obj = window.moveTarget;
  evt = evt ? evt : event ? event : null;
  if (evt == null) return false;    
  if (obj.className.match(/moving/) && (evt.button == 1|| evt.which == 19)) {
    var y = evt.pageY ? evt.pageY : event.clientY + document.body.scrollTop;
    var obj = getObject(gridId);
    var height = "currentStyle" in obj ? obj.currentStyle.height : getComputedStyle(obj, null).height;
    height = parseInt(height) + y - window.dragRefY;
    if (height > 20) getObject(gridId).style.height = height + "px";
    getObject(gridId).style.overflow="auto";
    window.dragRefY = y;
    unselectDocument();
    document.body.style.cursor = 'n-resize';
  }
}
/*
* One of three functions to dynamically resize the detail pane.
*/
function dropDetailPane() {
  if (!window.moveTarget) return false;
  var obj = window.moveTarget;
  obj.className = obj.className.replace(/ *moving/g, '');
  document.body.style.cursor = 'default';
}
/**
* Minimize or maximize detail pane.
*/
function resizeDetailPane(action) {
  var container = getObject('portalContent');
  if (!container) return false;
  container.className = container.className.replace(/( *maximized)|( *minimized)/g, '');
  switch (action) {
      case PANE_MINIMIZE :
          container.className += ' minimized';
          break;
      case PANE_MAXIMIZE :
          container.className += 'maximized';
          if (iframeForm = getObject('internalFormIframe')) {
            window.internalFormIframeHeight = iframeForm.style.height;
            iframeForm.style.height = (getObject('mainContent').offsetHeight - 180) + 'px';
          }
          break;
      case PANE_RESTORE :
          if (iframeForm = getObject('internalFormIframe')) {
            iframeForm.style.height = internalFormIframeHeight;
          }
        break;
  }
}

function setGMTOffset() {
  var d = new Date();
  backgroundPageNoHistory('/public/portal/setuserenvironment.none?apf.gmtOffsetMinutes='+d.getTimezoneOffset());
}
function loadModeler(id,version){
  if (typeof timeOut == 'number') clearTimeout(timeOut);
  if (getIEVer()>=6.0) {
    var h = screen.availHeight;
    var w = screen.availWidth;
    if (w > 1800) w/=2;
    if(id){
      if(version){
        processModeler = popup("/process/startdesigner.none?idToOpen="+id+"&versionToOpen="+version,'processModeler','top='+(h*.015)+',left='+(w*.03)+',width='+(w*.94)+',height='+(h*.94)+',scrollbars=no',true);
      }else{
        processModeler = popup("/process/startdesigner.none?idToOpen="+id,'processModeler','top='+(h*.015)+',left='+(w*.03)+',width='+(w*.94)+',height='+(h*.94)+',scrollbars=no',true);
      }
    }else{
      processModeler = popup("/process/startdesigner.none",'processModeler','top='+(h*.015)+',left='+(w*.03)+',width='+(w*.94)+',height='+(h*.94)+',scrollbars=no',true);
    }
    window.parent.processModeler = processModeler;
  } else {
    asi.alert("Sorry, the Process Modeler application is only compatible with Internet Explorer 6.0.");
  }
}
function logout() {
  if (typeof processModeler == 'undefined') {
    processModeler = window.parent.processModeler;
  }
  if (typeof processModeler == 'undefined' || processModeler.closed ) {
    document.location.href=CONTEXT_PREFIX + "logout.do";
  } else {
    asi.confirm("Logging out will close the process modeler.",logoutAndClose);
  }
}
function logoutAndClose() {
  processModeler.onunload='';
  processModeler.close();
  document.location.href=CONTEXT_PREFIX + "logout.do";
}
// inflight diagram
function loadModeler2(processId,processModelId,isEdit){ 
  if (typeof timeOut == 'number') clearTimeout(timeOut);
  if (getIEVer()>=6.0) {
    var h = screen.availHeight;
    var w = screen.availWidth;
    if (w > 1800) w/=2;
		if (typeof processModelId == 'undefined')	{
      processModeler = popup("/process/startdesigner.none",'processModeler','top='+(h*.015)+',left='+(w*.03)+',width='+(w*.94)+',height='+(h*.94)+',scrollbars=no',true);
    }else{
	    processModeler = popup("/process/startdesigner.none?processId="+processId+"&processModelId="+processModelId+"&isEdit="+isEdit,'processModeler','top='+(h*.015)+',left='+(w*.03)+',width='+(w*.94)+',height='+(h*.94)+',scrollbars=no',true);
    }
    window.parent.processModeler = processModeler;
  } else {
    asi.alert("Sorry, the Process Modeler application is only compatible with Internet Explorer 6.0.");
  }
}
/**
* Retrieves input elements of a certain type from a container.
* 
* @param type      The type of input to search for
* @param container (optional) The container to search in.  Defaults to the whole
*                  document.
* @return An array of input elements of the given type.  Empty array when there
*         are no results.
* @since 4.0
*/
function getInputsByType(type, container) {
  var result = new Array();
  container = container || document;
  if (type.toUpperCase() == 'SELECT') return container.getElementsByTagName('SELECT');
  if (type.toUpperCase() == 'TEXTAREA') return container.getElementsByTagName('TEXTAREA');
  var inputs = container.getElementsByTagName('INPUT');
  for (var i = 0; i < inputs.length; i++) {
    if (inputs[i].type == type) {
        result[result.length] = inputs[i];
    }
  }
  return result;
}
/**
* Retrieves input elements with a certain name.  Unlike calling form.name, this
* always returns an array, which may simplify your logic.
*
* @param type      The name of the input(s) you want to retrieve
* @param container (optional) The container to search in.  Defaults to the whole
*                  document.
* @return An array of input elements with the given name
* @since 4.0
*/
function getInputsByName(name, container) {
  var results = new Array();
  container = container || document;
  var inputs = container.getElementsByTagName('INPUT');
  for (var i = 0; i < inputs.length; i++) {
    if (inputs[i].name == name) results.push(inputs[i]);
  }
  return results;
}
/**
* Fetches an object or its style by ID.
*
* @param objectId   The ID of the object to be retrieved
* @param isGetStyle (optional) Set to true if you want to get the object's
*                   style instead of the object itself.
* @return The object with the given ID or its style.
* @since 4.0
*/
function getObject(objectId, isGetStyle) {
  var object = document.getElementById(objectId);
  if(object) return isGetStyle ? object.style : object;
  else return null;
}
/**
* Filters nodes that are not of type "element" out of the array.  Useful when
* you don't want to deal with text nodes, for example.
*
* @return nodeArray with non-element nodes filtered out
* @since 4.0
*/
function getNodeElements(nodeArray) {
  var nodeElements = new Array();
  for (var i = 0; i < nodeArray.length; i++) {
    if (nodeArray[i].nodeType == 1) nodeElements.push(nodeArray[i]);
  }
  return nodeElements;
}
/**
* Shorthand for removing an element from the document
*
* @since 4.0
*/
function removeElement(element) {
  element.parentNode.removeChild(element);
}
/**
* Cleans out an element.  Same effect as element.innerHTML = '', but more W3C
* compliant.
*
* @since 4.0
*/
function removeAllChildNodes(element) {
 while (element.hasChildNodes()) element.removeChild(element.childNodes[0]);
}
/**
* Gets the first tag in the specified container with the given tag name in or
* appends one if none exist.
*
* @param tagname   The kind of tag you want to retrieve, like 'DIV' or 'INPUT'
* @param container The container of the tag you want to retrieve, or
*                  the container in which you want to create the tag.  Defaults
*                  to the entire document.  Technically optional, but we're not
*                  sure why you'd ever want the default behavior.
* @return An element matching the tag name
* @since 4.0
*/
function getOrCreateElement(tagname, container) {
 container = container || document;
 var elements = container.getElementsByTagName(tagname);
 if (elements.length == 0) {
     var element = document.createElement(tagname);
     container.appendChild(element);
 } else element = elements[0];
 return element;
}
/**
* Gets all elements with the specified class name.
*
* @param name      The name that an element's class attribute must contain to be
*                  returned.
* @param container (optional) The container to restrict the search to.  Defaults
*                  to the entire document
* @param tagName   (optional) Used to restrict the search to elements with a
*                  specific tag name, like 'DIV' or 'INPUT'.
* @return An array of elements
* @since 4.0
*/
function getElementsByClassName(name, container, tagName){
  var all_obj,ret_obj=new Array();
  var container = container || document;
  all_obj = container.all ? (tagName ? container.getElementsByTagName(tagName): container.all) : container.getElementsByTagName(tagName || '*');
  for(var i=0;i<all_obj.length;i++){
    if(all_obj[i].nodeType == 1 && all_obj[i].className.indexOf(name) != -1){
      ret_obj[ret_obj.length]=all_obj[i];
    }
  }
  return ret_obj;
}

/**
* Retrieves all elements with a particular attribute value.
* 
* @param container (optional) The container to restrict the search to.  Defaults
*                  to the entire document
* @param tagName   (optional) Used to restrict the search to elements with a
*                  specific tag name, like 'DIV' or 'INPUT'.
* @return An array of elements with the indicated attribute values.
* @since 4.0
*/
function getElementsByAttributeValue(attribute, value, container, tagName) {
  var container = container || document;
  var elements = container.all ? (tagName ? container.getElementsByTagName(tagName): container.all) : container.getElementsByTagName(tagName || '*');
  var result = new Array();
  for (var i = 0; i < elements.length; i++) {
    if (elements[i].getAttribute(attribute) && elements[i].getAttribute(attribute) == value) {
        result.push(elements[i]);
    }
  }
  return result;
}
/**
* Retrieves the object's most direct container with the given tag name.
*
* @param maxDepth (optional) The number of attempts before the function gives
*                 up.  Specify to avoid excessive processing.  A maxDepth of -1
*                 (the default) will keep bumping the search up a parent until
*                 it finds either a match or the body tag.
* @return A parent matching the tagName, null if no match is found.
* @since 4.0
*/
function getContainerByTagName(object, tagName, maxDepth) {
  var container = object;
  maxDepth = maxDepth || -1;
  while (maxDepth != 0 && container && container.tagName != tagName.toUpperCase() && container.tagName != 'BODY') {
    container = container.parentNode;
    maxDepth--;
  }
  return container.tagName == tagName.toUpperCase() ? container : null;
}
/**
* Retrieves the object's most direct container with the given class name.
*
* @param name     The name that an element's class attribute must contain to
*                 constitute a match.
* @param maxDepth (optional) The number of attempts before the function gives
*                 up.  Specify to avoid excessive processing.  A maxDepth of -1
*                 (the default) will keep bumping the search up a parent until
*                 it finds either a match or the body tag.
* @return A matching parent, null if no match is found.
* @since 4.0
*/
function getContainerByClassName(object, name, maxDepth) {
  var container = object;
  maxDepth = maxDepth || -1;
  while (maxDepth != 0 && container && container.className.indexOf(name) == -1 && container.tagName != 'BODY') {
    container = container.parentNode;
    maxDepth--;
  }
  return container.className.indexOf(name) > -1 ? container : null;
}
/**
* Retrieves the stylesheet with the given name.
*
* @param name The name of the style sheet to retrieve, with the extension but
*             without the filepath.
* @return A JavaScript stylesheet object.
* @since 4.0
*/
function getStyleSheet(name) {
  for (var i = 0; i < document.styleSheets.length; i++) {
   var sheet = document.styleSheets[i];
   if(!sheet.cssRules) sheet.cssRules = sheet.rules;
   var href = sheet.href;
   sheet.name = href.substr(href.lastIndexOf('/') + 1, href.length);
   if(sheet.name == name) return sheet;
  }
  return false;
}
/**
* Toggle the display of each argument, where each argument is an element id
*
* @since 4.0
*/
function toggleDisplays() {
  for (var i = 0; i < arguments.length; i++) {
    toggleDisplay(arguments[i]);
  }
}
/**
* Interchanges the visibility of two elements.
*
* @param bool When true, the first object is shown and the second one is
*             hidden. Guess what happens when it's false?
* @param id1  The id of the first object. Also accepts a reference to the object.
* @param id2  The id of the second object.  Also accepts a reference to the object.
* @since 4.0
*/
function booleanToggle(bool, id1, id2) {
  var style1 = (typeof id1 == 'string') ? getObject(id1, true) : id1.style;
  var style2 = (typeof id2 == 'string') ? getObject(id2, true) : id2.style;
  if (eval(bool)){
    style1.display = '';
    style2.display = 'none';
  }
  else{
    style1.display = 'none';
    style2.display = '';
  }
}
/**
* Adds "hover" to the CSS class name of the children of an element while you are
* hovering over it.  This function includes a hack/fix for Netscape browsers,
* if you notice that you unexpectedly lose the hover effect ask Max Lin or
* Francisco Brito for details on leveraging it.
*
* @param element  The container for the things that will be hovered over
* @param nodeName The tagName of the children that will receive the hover
*                 effect.
* @param cascade  A boolean.  If false, attachHover will only affect direct
*                 children.  False by default.
* @since 4.0
*/
function attachHover(element, nodeName, cascade) {
  if (element) {       
    var root = element;
      for (var i=0; i<root.childNodes.length; i++) {
        var node = root.childNodes[i];
        if (node.nodeName==nodeName && node.className != 'divider') {
          node.onmouseover=function() {
            this.className+=" hover";
            if(navigator.appName == "Netscape" && element.id == 'netscapeOverflowHackTrigger') getObject('netscapeOverflowHackTarget').style.overflow = 'hidden';
          }
          node.onmouseout=function() {
            this.className=this.className.replace(/ *hover/g, "");
            if(navigator.appName == "Netscape" && element.id == 'netscapeOverflowHackTrigger') getObject('netscapeOverflowHackTarget').style.overflow = 'auto';
          }
        }
        if (cascade && node.nodeType == 1) {
            attachHover(node, nodeName, true);
      }
    }
  }
 }
/**
* Takes all direct child nodes of the container and alternately gives them the
* style class "alternate", starting with the first child.
*
* @param container The direct parent container of the nodes that might be given
*                  the style class "alternate".
* @param blockSize (optional) Tricky to describe.  blockSize = 1 results in
*                  101010...  blockSize = 2 results in 11001100...  Defaults to
*                  1.
* @since 4.0
*/
function alternateChildClasses(container, blockSize) {
  blockSize = blockSize || 1;
  var items = getNodeElements(container.childNodes);
  for (var i = 0; i < items.length; i++) {
    var item = items[i];
    if (Math.floor(i/blockSize) % 2) item.className += ' alternate';
    else item.className = item.className.replace(/ *alternate/g,'');
  }
}
/**
* Centers the object in the user's screen. The object will be centered via the top and left
* attributes in case it is absolutely positioned. Otherwise, top and left margins will be applied to it.
* 
* IE will sometimes report currentStyle as 'null', if it hasn't rendered the object. In
* this case, the call to this function will fail and return zero.
* @since 4.0
*/
function centerInViewport(object) {
  var scrolledFromTop = getScrollFromTop();
  var height = getWindowHeight();
  var bodyWidth = document.body.offsetWidth;
  try {var position = object.style.position != '' ? object.style.position : object.currentStyle ? object.currentStyle.position : getComputedStyle(object, null).position;
  } catch(e) {return 0}
  var objectHeight = object.style.height != '' ? object.style.height : object.currentStyle ? object.currentStyle.height : getComputedStyle(object, null).height;
  objectHeight = objectHeight.substr(0, objectHeight.indexOf('px'));
  var objectWidth = object.style.width != '' ? object.style.width : object.currentStyle ? object.currentStyle.width : getComputedStyle(object, null).width;
  objectWidth = objectWidth.substr(0, objectWidth.indexOf('px'));
  if(position != 'absolute'){
    object.style.marginTop =  (height - objectHeight) / 2 + 'px';
    object.style.marginLeft = (bodyWidth - objectWidth) / 2 + 'px';
  } else{
    object.style.top = ((height - objectHeight) / 2) + scrolledFromTop + 'px';
    object.style.left = (bodyWidth - objectWidth) / 2 + 'px';
  }
  return scrolledFromTop;
}
/**
* Retrieves the height of the browser window
* @since 4.0
*/
function getWindowHeight() {
  var windowHeight=0;
  if (typeof(window.innerHeight)=='number') {
    windowHeight=window.innerHeight;
  }
  else {    
    windowHeight = 
      (document.documentElement && document.documentElement.clientHeight != 0) ?
      document.documentElement.clientHeight :
      ((document.body&&document.body.clientHeight) ? document.body.clientHeight : 0);
  }
  return windowHeight;
}
/**
* Returns the distance that the document has been scrolled from the top
* @since 4.0
*/
function getScrollFromTop() {
  if(document.documentElement) return document.documentElement.scrollTop;
  if(window.scrollX) return window.scrollX;
  if(document.body.scrollHeight) return document.body.scrollTop;
  return 0;
}
/**
* Returns the height of the document
* @since 4.0
*/
function getDocumentHeight() {
   var sH = document.body.scrollHeight;
   var oH = document.body.offsetHeight;
   if (sH > oH) return sH;
   else return oH;
}
function showObject(objectId){
  theObject = getObject(objectId,"1");
  theObject["display"] = "inline";
}

function showObjectNull(objectId){
  theObject = getObject(objectId,"1");
  theObject["display"] = "";
}

function showObjectBlock(objectId){
  theObject = getObject(objectId,"1");
  theObject["display"] = "block";
}

function hideObject(objectId){
  theObject = getObject(objectId,"1");
  theObject["display"] = "none";
}
/**
* Shows or hides the object depending on its previous state
*/
function toggleDisplay(objectId, displayType){
  theObject = getObject(objectId,"1");
  if(theObject["display"] == "none"){
    theObject["display"] = displayType || "inline";
    return true;
  }
  else{
    theObject["display"] = "none";
    return false;
  }
}
function setWidth(o,x) {
  if (x>0)
    o.style.width = x+"px";
}
/**
* Gives the distance (in pixels) of the object from the left of the screen.
*
* @param element      The object whose distance will be measured
* @param centerFactor (optional) A boolean or number.  Best explained via
*                     example: when set to 0.5, you will get the distance from
*                     the CENTER of the element to the left of the screen instead
*                     of the LEFT of the element to the top of the screen.  True
*                     is equivalent to 0.5.  False or null is equivalent to 0.
* @return the distance from the element to the left of the screen as a string
* @since 4.1
*/
function getPageOffsetLeft(element, centerFactor) {
  var offsetLeft = element.offsetLeft;
  if(centerFactor){
    if(typeof centerFactor != 'number') centerFactor = 1/2;  
    offsetLeft += Math.floor(element.offsetWidth * centerFactor);
  }
  while ((element = element.offsetParent) != null) { offsetLeft += element.offsetLeft;}
  return offsetLeft;
}
/**
* Gives the distance (in pixels) of the object from the top of the screen.
*
* @param element      The object whose distance will be measured
* @param centerFactor (optional) A boolean or number.  Best explained via
*                     example: when set to 0.5, you will get the distance from
*                     the CENTER of the element to the top of the screen instead
*                     of the TOP of the element to the top of the screen.  True
*                     is equivalent to 0.5.  False or null is equivalent to 0.
* @return the distance from the element to the top of the screen as a string
* @since 4.1
*/
function getPageOffsetTop(element, centerFactor) {
  var offsetTop = element.offsetTop;
  if(centerFactor){
    if(typeof centerFactor != 'number') centerFactor = 1/2;  
    offsetTop += Math.floor(element.offsetHeight * centerFactor);
  }
  while ((element = element.offsetParent) != null) { offsetTop += element.offsetTop;}
  return offsetTop;
}
/**
* Returns true if the element is invisible
*
* @param element The element whose visibility you want to gauge
* @return A boolean
* @since 4.1
*/
function isVisible(element){
  if(element.offsetWidth == '0' || element.offsetHeight == '0') return false;
  else return true;
}
/**
* Gets the row elements (TR) in the table bodies (TBODY)
* If an exclusion class is specified, any row with that class will not be returned.
* @return An array of row elements minus rows with exclusionClass
* @since 4.0
*/
function getDataRows(table, exclusionClass) {
  var dataRows = new Array();
  for (var i = 0; i < table.tBodies.length; i++) {
    for (var j = 0; j < table.tBodies[i].rows.length; j++) {
      var row = table.tBodies[i].rows[j];
      if (!exclusionClass || !row.className.match(new RegExp(exclusionClass))) dataRows = dataRows.concat(row);
    }
  }
  return dataRows;  
}
/**
* Alternately gives table rows the style class "alternate", starting with the
* first row.  Only table rows in the table body will be affected.
*
* @param blockSize (optional) Tricky to describe.  blockSize = 1 results in
*                  101010...  blockSize = 2 results in 11001100...  Defaults to
*                  1.
* @since 4.0
*/
function alternateRowClass(table, blockSize) {
  blockSize = blockSize || 1;
  for (var i = 0; i < table.tBodies.length; i++) {
    for (var j = 0; j < table.tBodies[i].rows.length; j++) {
      var row = table.tBodies[i].rows[j];
      row.className = row.className.replace(/ *alternate/g,'');
      if (Math.floor(j/blockSize) % 2) row.className += ' alternate';
    }
  }
}
/**
* Helps ensure all table rows have the same number of cells by giving a colspan
* attribute to one-cell rows.  The colspan value is equal to the maximum number
* of cells contained in an existing row.
*
* @since 4.0
*/
function normalizeColumns(table) {
  var numCols = 1;
  for (var i = 0; i < table.rows.length; i++) {
    var numCells = table.rows[i].cells.length;
    numCols = numCells > numCols ? numCells : numCols;
  }
  for (var i = 0; i < table.rows.length; i++) {
    var numCells = table.rows[i].cells.length;
    if (numCells == 1 && numCells < numCols){
      table.rows[i].cells[0].colSpan = numCols;
    }
  }
}

//GRID SPECIFIC FUNCTIONS//
/**
* Gets all selected rows: TR elements with class = 'selected'
* @return An array of row elements
* @since 4.0
*/
function getSelectedRows(container) {
  return getElementsByClassName('selected', container, 'TR');
}
/**
* Marks the row using the pseudo class "selected" and checks 
* the first checkbox found in the row. This checkbox is essential for
* toggling the row selection. An unselect command is issued because when
* ctrl-clicking, parts of the document gets selected, which looks pretty bad.
* 
* selectRow() will return without further execution if the event is generated
* within an anchor.
* 
* @param row
* @param isExclusive boolean indicating whether or not to unselect all rows first.
*                                 This is overridden as 'false' if the Ctrl key is pressed when selecting.
* @param evt the explicit event object. It must always be passed as 'event' or 'false'
*                      to bypass the event generation detection.
* @since 4.0
*/
function selectRow(row, isExclusive, evt) {
  evt = (typeof evt != 'undefined') ? evt : (event) ? event : false;
  var target = null;
  if (typeof evt != "undefined" && evt != false) {
    if(evt.ctrlKey) unselectDocument();
    if(evt.ctrlKey) isExclusive = false; // ctrl-click for multiple select
    target = (evt.target) ? evt.target : event.srcElement;
    if (target.tagName == 'A' && target.href.length > 0 && !target.href.match(/#$/)) return false; //disable selectRow if clicked on link
  } else if (evt != false) ASI_LOG.warn("Row selection must explicitly get the event object for non-IE browsers. Unexpected behavior will stem from this omission.%0a Try using autoClick(element) instead. ", true);
  var checkbox = getCheckboxInElement(row);
  if (checkbox == "null") throw new Error('Row cannot be selected without a checkbox');
  isChecked = checkbox.checked;
  checkboxContainer = getContainerByTagName(checkbox, 'TABLE');
  if (isExclusive) unselectRows(checkboxContainer);
  if (isExclusive) isChecked = false;
  if (target && (target.tagName == 'INPUT' || target.tagName == 'SELECT' || target.tagName == 'TEXTAREA'))  isChecked = !isChecked;
  if (isChecked) row.className = row.className.replace(/ *selected/g, '');
  else  row.className += ' selected';
  checkbox.checked = !isChecked;
  return true;
}

/**
* Appends the pseudo class "selected" to the row and checks the first checkbox
* found in it. This function should NOT be called by an onclick event, it is only for
* rendering purposes. For functional purposes, only selectRow should be called.
* 
* Note that calling this function is almost equivalent to calling selectRow(row, false, false)
* and this is generally preferred.
* @since 4.0
*/
function highlightRow(row) {
  if(!row.className.match(/selected/g)){
    row.className += ' selected';
    getCheckboxInElement(row).checked = true;
  }
}

/**
* Unselects all the selected rows in the container
* @since 4.0
*/
function unselectRows(container) {
  var rows = getSelectedRows(container);
  for (var i = 0; i < rows.length; i++) {
    rows[i].className = rows[i].className.replace(/ *selected/g, '');
    var checkbox = getInputsByType('checkbox', rows[i])[0];
    if (!checkbox) continue;
    checkbox.checked = false;
  }
}
/**
* Selects all rows in the container
* @since 4.0
*/
function selectAllRows(container) {
  var rows = getDataRows(container);
  for (var i = 0; i < rows.length; i++) {
    var checkbox = getInputsByType('checkbox', rows[i])[0];
    if (!checkbox) continue;
    selectRow(rows[i], false, false);
  }
}
// ============ GENERAL UTILITY FUNCTIONS ==================
/**
* Simulates a user click on the given element. This click-event object is used
* by functions like selectRow.  element.onclick() also returns a click-event
* object, but it's not browser compatible.
*
* @param element The element which you want to simulate clicking
* @return A click event object
* @since 4.0
*/
function autoClick(element) {
  if (document.createEvent){
    var evt = document.createEvent("MouseEvents");
    evt.initMouseEvent('click', true, true, document.defaultView, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
    return element.dispatchEvent(evt);
  }
  else if (element.tagName) return element.click();
}
/**
* The Array.join() for an arguments object.  Even though you can do things like
* arguments[i], arguments is not an actual Array and thus lacks the join
* method.  Joins the arguments into a string using the indicated delimiter or
* "/" if unspecified.
* 
* @param functionArgs the arguments object to be converted into a string
* @param delimiter the delimiter for the returned string. Defaults to "/";
* @return A string with all of the argument object's items in it.
* @since 4.0
*/
function joinArgs(functionArgs, delimiter) {
  delimiter = delimiter || "/";
  var buffer = "";
  for (var i = 0; i < functionArgs.length; i++) {
    buffer += i > 0 ? delimiter : "";
    buffer += functionArgs[i];
  }
  return buffer;
}
/**
* The Array.slice() for an arguments object.  Even though you can do things
* like arguments[i], arguments is not an actual Array and thus lacks the join
* method.  Takes a subset of a function's arguments and returns them as a
* proper Array.
*
* @param functionArgs the arguments object to be converted into an array
* @param startIndex   the index of the first argument to be pushed onto the
                      array
* @param endIndex     the index of the last argument to be pushed onto the
                      array.  endIndex defaults to functionArgs.length.
* @return An array
* @since 4.0
*/
function sliceArgs(functionArgs, startIndex, endIndex) {
  var out = new Array();
  var endIndex = endIndex || functionArgs.length;
  for (var i = startIndex; i < endIndex; i++) {
    out[out.length] = functionArgs[i];
  }
  return out;
}
/**
* Used by developers to diagnose lengthy backend execution.  It updates the
* mouse cursor to let the developer know that there is something in progress.
* It also writes the elapsed code execution time to the Logger.
* To use it, call cogitate(true) at the start of the code to be executed, then
* call cogitate(false) at the end of the code.  Will not work if the page
* is completely refreshed in between the two calls.
*
* @param isStart Boolean indicating whether this is the first, opening call to
*                cogitate, or the second, closing call.
* @return Returns true.
* @since 4.0
*/
function cogitate(isStart) {
  if (isStart) {
    document.body.className += ' cogitating';
    window.status = 'Working...';
    SYS_START = new Date().getTime();
  } else {
    document.body.className = document.body.className.replace(/ cogitating/,'');
    SYS_END = new Date().getTime();
    var e = (SYS_END - SYS_START)/1000;
    ASI_LOG.debug("Elapsed: " + e + " seconds");
    window.status = 'Done';
  }
  return true;
}
/**
* Pauses code execution for the given number of milliseconds.
*
* @param ms The number of milliseconds to pause execution
* @since 4.0
*/
function wait(ms) {
  var start = new Date().getTime();
  while (new Date().getTime() < (start + ms)){}
}
/**
* Unselects text from the current document. Browsers sometimes auto-highlight,
* which can be undesirable.
*
* @since 4.1
*/
function unselectDocument() {
  try{document.execCommand("Unselect", false, null);} 
  catch (e){} //not properly supported in NS
}
function getSetContents(obj1,obj2){
  try{obj2.innerHTML = obj1.innerHTML;}catch(e){}
}
/**
* Disable Enter if the event is not originating from a textarea
* close asiDialog on Esc if found.
*
* @since 4.0
*/
function disableEnter() {
  if (document.layers) document.captureEvents(Event.KEYPRESS);
  document.onkeypress = doKey;
}
/**
* Returns a generic object with a property named after each param specified.
*
* @since 4.1SP2
*/
function set(){
  var mySet = {};
  for(var i=0; i<arguments.length; i++){
    mySet[arguments[i]] = '';
  }
  return mySet;
}
function getIEVer() {
  if (window.clientInformation==null) return false;
  if (window.clientInformation.appVersion==null) return false;
  return window.clientInformation.appVersion.match(/MSIE ([0-9].[0-9])/i)[1];
}
function applyCN() {
  importStyleSheet('/portal/css/skins.css');
  document.body.className = 'default ' + getObject('portalQueryField').value;
}
// ============ CORE OBJECT PROTOTYPES ==================
/**
* Returns whether or not this string starts with the string s.
*
* @param s A string
* @return A boolean
* @since 4.0
*/
String.prototype.startsWith = function (s) {
  return this.indexOf(s)==0; 
}

/**
* Returns whether or not this string ends with the string s
*
* @param s A string
* @return A boolean
* @since 4.0
*/
String.prototype.endsWith = function (s) {
  return this.lastIndexOf(s)==this.length-s.length; 
}

/**
* Returns whether or not this string equals the string s (case-insensitive)
*
* @param s A string
* @return A boolean
* @since 4.0
*/
String.prototype.equalsIgnoreCase = function (s){
  return !s ? false : this.toLowerCase()==s.toLowerCase();
}

/**
* Returns the indexth element in the string, where elements are delimited by
* given the delimiter.
*
* @param index     The index of the element you want to return
* @param delimiter (optional) The character used to separate "elements" in the string.
*                  '/' by default.
* @return The substring at the specified index
* @since 4.0
*/
String.prototype.tokenAt = function (index, delimiter) {
  if (typeof index == 'undefined') return this;
  return this.split(delimiter || "/")[index];
}
/**
* Treating a string as a slash-separated array, this function is analagous
* to the splice function.
*
* @param startIndex   The index of the first "array" element to return
* @param includeCount The number of "array" elements to splice
* @param delimiter    (optional) The character used to separate elements within the
*                     string.  '/' by default.
* @return A substring of the original string
* @since 4.0
*/
String.prototype.tokenizedSubstring = function (startIndex, includeCount, delimiter) {
 var tokens = this.split(delimiter || "/");
 return tokens.splice(startIndex, includeCount).join(delimiter || "/");
}
/**
* Treating a string as a slash-separated array, replaces the indexth element
* with the specified value.
*
* @param index     The index of the value to be replaced
* @param value     The new value.  An empty string is acceptable.
* @param delimiter (optional) The character used to separate elements within the string.
                   '/' by default.
* @since 4.0
*/
String.prototype.replaceToken = function (index, value, delimiter) {
 var tokens = this.split(delimiter || "/");
 var subsetA = tokens.slice(0, index);
 if (typeof value != "undefined") subsetA.push(value);
 return subsetA.concat(tokens.slice(index + 1)).join(delimiter || "/");
}
/**
* Validates the string against a regular expression.  The RE used depends on the
* value of 'type'.
*   - "positive": whole numbers {0, 1, 2, 3,  ...}
*   - "long": integers {..., -1, 0, 1, ...}
*   - "positivefloat": positive rational numbers
*   - "float": rational numbers
*   - "scientific": rational numbers, possibly ending with E and then an integer
*                   exponent.  For example, 6.5E-3 is equivalent to .0065
*   - "alphanumeric": contains only digits, letters and underscores
*   - "date": Depends on local settings.  Inspect the globally-scoped JS variable
*             datePickerPattern to see what holds for your client.
*   - "time": Depends on local settings.  Inspect the globally-scoped JS variable
*             timePickerPattern to see what holds for your client.
*   - "datetime": date + time
*   - "zipcode": five digits numbers
*   - "ssn": nine digit numbers with optional dashes anywhere
*   - "email": e-mail address (domain is not verified)
* 
* @param type                         The RE used to validate the string
* @param isMultipleAllowed (optional) if true, splits the string using commas
                                      and validates each piece instead of the
                                      whole string.
* @return True if the string is validates against the RE, false otherwise
* @since 4.0
*/
String.prototype.validate = function (type, isMultipleAllowed) {
  if (isMultipleAllowed) {
    var strings = this.toArray(",");
    for (var i = 0; i < strings.length; i++) {
      if (!strings[i].validate(type)) return false;
     }
    return true;
  }
  var trimmed=this.trim();
  switch (type) {
    case "positive" : return trimmed.match(/^\d+$/)!=null;
    case "long" : return trimmed.match(/^(-)?\d+$/)!=null;
    case "positivefloat" : return trimmed.match(/^((\d+(\.\d*)?)|(\.\d+))$/)!=null;
    case "float" : return trimmed.match(/^(-)?((\d+(\.\d*)?)|(\.\d+))$/)!=null;
    case "scientific" : return trimmed.match(/^(-)?((\d+(\.\d*)?)|(\.\d+))(E(-)?\d+)?$/i)!=null;
    case "alphanumeric" : return trimmed.match(/^\w+$/)!=null;
    case "date" :
    case "time" :
    case "datetime" :
      return validateDatetime(trimmed, type);
    case "zipcode" : return trimmed.match(/^\d{5}(-\d{4})?$/) != null;
    case "ssn" : return trimmed.replace(/-/g,'').match(/^\d{9}$/) != null;
    case "email" : return trimmed.match(/^[\w\d._%-]+@[\w\d._%-]+\.[\w\d._%-]{2,4}$/) != null;
    case "boolean" : return trimmed.match(/^(yes|no)$/i) != null;
    default : return true;
  }
}
/**
* Splits a string into an array.  Unlike String.prototype.split, it overlooks
* delimiters within quoted strings.  A quoted string is anything sans unescaped
* quotes sandwiched in double quotes, then immediately sandwiched by delimiters.
*   - ...,"Hello",... IS a quoted string
*   - ...,"Hello,... IS NOT a quoted string
*   - ...,"Hel"lo",... IS NOT a quoted string
*   - ...,"Hel\"lo",... IS a quoted string
*   - ...,H"ell"o,... IS NOT a quoted string
* 
* @param delim (optional) the delimiter used to split the string. Defaults to ",".
* @param return           An array representing the string.
* @since 4.0
*/
String.prototype.toArray = function (delim) {
  delim = delim || ',';
  var results=new Array();
  var regex=new RegExp("(((\\s)*\\\"((\\\\(\\\")?)|[^\\\"])*\\\"(\\s)*)|([^"+delim+"]+))("+delim+"|$)","g");
  var result;
  while((result = regex.exec(this)) && results.length<100000) {
    var current=result[0];
    if(current.endsWith(delim)) current=current.slice(0, -1);
    current=current.trim();
    if(current.length > 0) {
      current=unquote(current);
      results.push(current);
    }
  }
  return results;
  function unquote(str) {
    if(str.startsWith('"') && str.endsWith('"')) str=str.slice(1, -1);
    return str.replace(/\\\"/g,"\"");
  } 
}
/**
* Removes white space from the start and the end of the string.
*
* @return The trimmed string
* @since 4.0
*/
String.prototype.trim = function () {
  return this.replace(/^\s+/,'').replace(/\s+$/,'');
}
function trim(a){
  return a.replace(/^\s+/,'').replace(/\s+$/,'');
}
/**
* Creates an asiAlert using the string as the message
*
* @since 4.0
*/
String.prototype.alert = function () {
  asi.alert(this);
}
/**
* Replaces potentially disruptive HTML characters with their escaped counterparts.
*
* @return The escaped string
* @since 4.0
*/
String.prototype.escapeHTML = function () {
  var s = this;
  s = s.replace(/&/g,"&amp;");
  s = s.replace(/</g,"&lt;");
  s = s.replace(/>/g,"&gt;");
  s = s.replace(/'/g,"&#39;");
  s = s.replace(/"/g,'&quot;');
  return s;
}
/**
* Unescapes HTML. Inverse of escapeHTML
*
* @return The unescaped string
* @since 4.1
*/
String.prototype.unescapeHTML = function () {
  var s = this;
  s = s.replace(/&lt;/g,"<");
  s = s.replace(/&gt;/g,">");
  s = s.replace(/&#39;/g,"'");
  s = s.replace(/&quot;/g,'"');
  s = s.replace(/&amp;/g, "&");
  return s;
}
/**
* Replaces potentially disruptive JS characters with their escaped counterparts.
* Usually used to doctor strings that will be double parsed by JavaScript.
*
* @return The escaped string
* @since 4.0
*/
String.prototype.escapeJS = function () {
  var s = this;
  s = s.replace(/\\/g,"\\\\");
  s = s.replace(/'/g,"\\'");
  s = s.replace(/"/g,'\\"');
  return s;
}
/**
* Replaces JS regular expression characters with their RE escaped values,
* so they will be interpreted by reg exps as mere text.
*
* @return The escaped string
* @since 5.0
*/
String.prototype.escapeRegEx = function () {
  var s = this;
  letters = ['\\','!','$','/','.','[',']','+','{','}','-','^','?','*','|','(',')'];
  for (var i=0; i < letters.length; i++) {
    var withSlash = '\\' + letters[i];
    s = s.replace(new RegExp(withSlash, 'g'), withSlash);
  }
  return s;
}
/**
* Truncates the string given the desired maximum length. Smart truncation allows
* this function to truncate at the last word boundary, so no word is left incomplete,
* unless the optional parameter "isHardTruncate" is specified. Be aware that the
* length does NOT take into consideration the trailing ellipses, so the final length
* will be (length + 3) or less, given the smart truncation.
* 
* @param length the maximum length allowed
* @isHardTruncate (optional) if true, the returned string may have incomplete 
* words in it, that is, the truncation will not look for a word boundary.
*/
String.prototype.truncate = function (length, isHardTruncate) {
  if (isHardTruncate) return this.length > length ? this.slice(0, length) + "..." : this;  
  return this.length > length ? this.slice(0, length).replace(/(.+?)\s*\b\w+$/, '$1...') : this;  
}
/**
* Checks if the given object is included in the array. Note that the comparison is
* not strict, where 1 is equal to "1".
*
* @return The index of the object, if found or -1 if not found.
* @since 4.0
*/
Array.prototype.indexOf = function (item) {
  for (var i = 0; i < this.length; i++) {
    if (this[i] == item) return i;
  }
  return -1;
}
/**
* Starting with an array of tokenized strings, used to find the index
* of the tokenized string that has a tokenized substring with a certain value.
* A tokenized string is one that contain multiple values separated by a
* delimiter.  
*
* @param searchTerm   A tokenized search string
* @param startIndex   The index of the first token that you want to compare
                      against the searchTerm within each array element
* @param includeCount The number of tokens that you want to pull out of each
*                     search candidate to match against the searchTerm
* @param delimiter    The character that separates elements in tokenized
*                     strings.  '/' by default.
* @return The index of the matching array element if found, -1 otherwise.
* @since 4.0
*/
Array.prototype.tokenizedIndexOf = function (searchTerm, startIndex, includeCount, delimiter) {
  for (var i = 0; i < this.length; i++) {
    if (this[i].tokenizedSubstring(startIndex, includeCount, delimiter || "/") == searchTerm) return i;
  }
  return -1;  
}
/**
* Same as Array.join(), only array elements containing a comma or double quote
* get wrapped in double quotes with all original double quotes escaped.
* 
* @param delim (optional) The delimiter used to join the array.  Defaults to
*                         comma.
* @return A delimiter-separated string of the array elements
* @since 4.0
*/
Array.prototype.quotedJoin = function (delim) {
  delim = delim || ',';
  for (var i=0;i<this.length;i++) {
    var curr=this[i];
    if(curr.match(/'"'|','/)) curr = quote(curr);
  }
  return this.join(delim);
  function quote(str) {
    return '"' + str.replace(/\"/g,"\\\"") + '"';
  }
}

/**
* Checks if the array contains the object. Note that the comparison is strict:
* where 1 is not equal to "1".
* 
* @obj the function will check if this object is contained in the array or not
* @return true if the object is in the array
*/
Array.prototype.contains = function (obj) {
  for (var i = 0; i < this.length; i++) {
    if (this[i] === obj) return true;
  }
  return false;
}


/**
* Inserts the value at the last location of the cursor within the target field.
* IE and Mozilla/Netscape only.
*
* @param myRange A JavaScript Range or TextRange object
* @param myValue The value to be inserted
* @since 4.0
*/
function insertAtCursor(myRange, myValue){
  var parent = myRange.parentElement ? myRange.parentElement() : myRange.startContainer;
  myRange.text = myValue;
  myRange.select();
}
/**
* Gives myField focus and shifts the cursor from its last location. IE only.
* 
* @param myField The field within which the cursor will be relocated
* @param steps   The number of steps to shift the cursor.  Positive steps to
*                push it forward, negative to go backwards.
* @since 4.0
*/
function moveCursor(myField, steps){
  myField.focus();
  var range = document.selection.createRange();
  range.move("character", steps);
  range.select();
}
/**
* Truncates the specified field to <maxlimit> characters
*
* @param field The field
* @param maxlimit The upper limit on the number of characters
* @since 4.0
*/
function limitInputLength(field, maxlimit) {
if (field.value.length > maxlimit)
field.value = field.value.substring(0, maxlimit);
}
/**
* Generates HTML for an image.
*
* @param src    The URL of the image's src.  You may omit the application
*               context in the URL.
* @param alt    Alt text for the image.
* @param altSrc (optional) The URL to use for the image's src when in low-bandwidth mode.
* @return Image tag HTML as a string
* @since 4.0
*/
function getImageHTML(src, alt, altSrc) {
  var imgSrc = isLowBandwidth && altSrc ? altSrc : src;
  var imgAlt = alt || "Decorative Image";
  var out = "<img src=\"" + rewriteURL(imgSrc) + "\" alt=\"" + imgAlt + "\"";
  out += " />";
  return out;
}
/**
* Generates HTML for an input element.  The fourth parameter is an optional
* string of custom tag attributes, like 'id="myPic" style="border: 0"'.
* 
* @param name  The name of the input
* @param value The default value of the input
* @param type  The type attribute of the input
* @return Input HTML as a string
* @since 4.0
*/
function InputHTML(name, value, type) {
  value = value || '';
  type = type || 'text';
  var out =  "<input type=\"" + type + "\" name=\"" + name + "\" value=\"" + value + "\"";
  if (arguments[3]) out+= arguments[3];
  out += " />";
  return out;
}
/**
* Generates HTML for a checkbox.  The fourth parameter is an optional
* string of custom tag attributes, like 'id="myBox" style="border: 0"'.
* 
* @param name      The name of the checkbox
* @param value     The value of the checkbox
* @param isChecked (optional) Boolean indicating default checked status.  False by default.
* @return Checkbox HTML as a string
* @since 4.0
*/
function CheckboxHTML(name, value, isChecked) {
  value = value || '';
  var out =  "<input type=\"checkbox\" name=\"" + name + "\" value=\"" + value + "\"";
  if (isChecked) out += " checked=\"true\"";
  if (arguments[3]) out+= arguments[3];
  out += " />";
  return out;
}
/**
* Generates and returns the markup for a radio element.
* A fourth parameter (optional) can be used to append other attributes.
* @return HTML for a Radio element
* @since 4.0
*/
function RadioHTML(name, value, isChecked) { 
  value = value || ''; 
  var out = "<input type=\"radio\" name=\"" + name + "\" value=\"" + value + "\"";
  if (isChecked) out += " checked=\"true\"";
  if (arguments[3]) out+= arguments[3];
  out += " />";
  return out;
}
/**
* Generates and returns the markup for a textarea element.
* A third parameter (optional) can be used to append other attributes.
* @return HTML for a Textarea element
* @since 4.1
*/
function TextareaHTML(name, value) { 
  value = value || ''; 
  var out = "<textarea name=\"" + name + "\" ";
  if (arguments[2]) out+= arguments[2];
  out += " >" + value + "</textarea>";
  return out;
}
/**
* Generates HTML for a select element.  The fourth parameter is an optional
* string of custom tag attributes, like 'id="mySelect" style="border: 0"'.
* 
* @param name  The name of the dropdown
* @param value The default value of the dropdown
* @param opts  An array of dropdown options in the format
*              [[display1, value1], [display2, value2], etc.]
* @return Select element HTML as a string
* @since 4.0
*/
function SelectHTML(name, value, opts) {
  value = value || '';
  var out = "<select name=\"" + name + "\" ";
  if (arguments[3]) out+= arguments[3];
  out += ">";
  for (var i = 0; i < opts.length; i++) {
    out += "<option value=\"" + opts[i][1] + "\" ";
    out += value == opts[i][1] ? 'selected=\"true\" ' : '';
    out +=">" + opts[i][0] + "</option>";
  }
  out += "</select>";
  return out;
}
/**
* Generates and returns the markup for an Editable Select element
* The Options are given after the first two arguments (name and value)
* in a single array in the form: [[name1, value1], [name2, value2]] etc.
* A fourth parameter (optional) can be used to append other attributes.
* @return HTML for an Editable Select element
* @since 4.0
*/
function EditableSelectHTML(name, value, opts) {
  value = value || 'Type value here';
  var out = "<div class=\"editableSelect\"><input name=\"" + name + "\" ";
  if (arguments[3]) out+= arguments[3];
  out += " value=\"" + value + "\" onclick=\"if (this.value == 'Type value here') this.value = '';\" /><div>";
  out += "<iframe scrolling=\"no\" frameBorder=\"1\" style=\"filter: alpha(opacity=0)\"></iframe></div><select"
  out += " onchange=\"this.parentNode.childNodes[0].value = (this.value == \'null\') ? \'\' : this.value;";
  out += " this.parentNode.childNodes[0].focus();try{this.parentNode.childNodes[0].onchange()}catch(e){}\">";
  out += "<option value=\"null\"></option>";
  for (var i = 0; i < opts.length; i++) {
    out += "<option value=\"" + opts[i][1] + "\" ";
    out += value == opts[i][1] ? 'selected=\"true\" ' : '';
    out +=">" + opts[i][0] + "</option>";
  }
  out += "</select></div>";
  return out;
}
/**
* Used to determine whether a form element is a field or not.  Usually used to
* filter fieldsets and buttons out of form.elements.
*
* @param field The 'field' that could potentially be an undesireable fieldset
*              or button.
* @return True if the field is an editable input
* @since 4.0
*/
function isField(field) {
  return field.tagName != 'FIELDSET' && field.type != 'button' && field.type != 'submit';
}
function getRadioValue(oRadioGrp, isDisplayValue){
 if (typeof oRadioGrp.length == 'undefined') {//single radio
  if (oRadioGrp.checked) {
    if(isDisplayValue) return getFieldLabel(oRadioGrp);
    return oRadioGrp.value;
  }
 } else { // radio group
  for (var i=0; i<oRadioGrp.length; i++){
    if (oRadioGrp[i].checked)   {
      if(isDisplayValue) return getFieldLabel(oRadioGrp[i]);
      return oRadioGrp[i].value;
    }
  }
 }
}
function getRadioGroupValue(name, isDisplayValue) {
  return getRadioValue(document.getElementsByName(name), isDisplayValue);
}

function getRadioValueByName(rNm){
  return getRadioValue(getObject(rNm));
}
function getBooleanTypeValue(name, isDisplayValue) {
  if (eval(getRadioGroupValue(name))) return isDisplayValue ? 'Yes' : 'true';
  else return isDisplayValue ? 'No' : 'false';
}
function getCheckboxInElement(object) {
  var fields = object.getElementsByTagName('INPUT');
  for (var i = 0; i < fields.length; i++) {
      if (fields[i].type == 'checkbox') {
          return fields[i];
      }
  }
}
function getSelectedCheckboxes(checkGroup) {
   var aResults = new Array();
   if (checkGroup[0]){
      for (var i=0; i<checkGroup.length; i++){
         if (checkGroup[i].checked){
            aResults[aResults.length] = i;
         }
      }
   } else {
      if (checkGroup.checked){
         aResults[0] = 0;
      }
   }
   return aResults;
}
function getSelectedCheckboxValues(checkGroup, isDisplayValue) {
   var aResults = new Array();
   var selectedItems = getSelectedCheckboxes(checkGroup);
   if (selectedItems.length != 0) {
      aResults.length = selectedItems.length;
      for (var i=0; i<selectedItems.length; i++){
         if (checkGroup[selectedItems[i]]){
            aResults[i] = 
              isDisplayValue ? getFieldLabel(checkGroup[selectedItems[i]]) : checkGroup[selectedItems[i]].value;
         } else{
            aResults[i] = 
              isDisplayValue ? getFieldLabel(checkGroup) : checkGroup.value;
         }
      }
   }
   return aResults;
}
function checkAll(checkGroup) {
   if (checkGroup[0]){
      for (var i=0; i<checkGroup.length; i++){
        checkGroup[i].checked = true;
      }
   } else {
     checkGroup.checked = true;
   }
}
function uncheckAll(checkGroup) {
   if (checkGroup[0]){
      for (var i=0; i<checkGroup.length; i++){
        checkGroup[i].checked = false;
      }
   } else {
     checkGroup.checked = false;
   }
}
function getFieldValue(field, isDisplayValue) {
  if (field.tagName == 'INPUT') {
    if (field.type == 'checkbox') {
        var checkboxValues = getSelectedCheckboxValues(document.getElementsByName(field.name), isDisplayValue).join();
        return isDisplayValue ? checkboxValues.replace(/\s\,/g,', ') : checkboxValues;
    }else if (field.type == 'radio' && field.className == 'booleanInput') {
      return getBooleanTypeValue(field.name, isDisplayValue);
    }else if (field.type == 'radio') {
      return getRadioGroupValue(field.name, isDisplayValue);
    }else if (getObject('pickerDisplayName_' + field.name) && isDisplayValue) {
      return getObject('pickerDisplayName_' + field.name).value;
    } else {
      return field.value;        
    }
  } else if (field.tagName == 'SELECT') {
    if(field.selectedIndex == -1) return;
    if (isDisplayValue) return field.options[field.selectedIndex].innerText || field.options[field.selectedIndex].innerHTML;
    else return  field.options[field.selectedIndex].value;
   return isDisplayValue ? field.options[field.selectedIndex].innerText : field.options[field.selectedIndex].value;
  } else if (field.tagName == 'TEXTAREA') {
      return field.value;
  }
}
/**
* @return True if the dropdown already contains an option with the value
*/
function isDuplicateValue(dropdown, value){
  var d = dropdown.options;
  for(var i=0; i<d.length; i++){
    if(d[i].value == value) return true;
  }
  return false;
}
/**
* Used to simulate clicking on a form's submit button when the user hits enter
* anywhere within a form.  Typically inserted into the onkeydown event of a
* form.  The algorithm searches first for an input of type
* submit, then for buttons with a value in the keywords array, which currently
* consists of 'Next' and 'Finish'.
*
* @param evt Pass in the JavaScript keyword 'event' (so without the quotes).
* @since 4.1
*/
function formListener(evt){
  var keywords = ['Next', 'Finish'];
  evt = evt ? evt : event;
  var key = evt.keyCode ? evt.keyCode : evt.which;
  var target = evt.srcElement ? evt.srcElement : evt.target;
  if (target.tagName == 'TEXTAREA') return;
  var form = target.form;
  try{
    switch (key) {
      case ENTER_KEY :
        var submit = getInputsByType('submit', form)[0];
        if (submit) autoClick(submit);
        else for (var i=0; i<keywords.length; i++){
          var candidate = getElementsByAttributeValue('value', keywords[i], form, 'INPUT')[0];
          if(candidate && candidate.type == 'button'){
            autoClick(candidate);
            break;
          }
        }
        break;
      case ESC_KEY :
        break;
    }
  } catch (e) {}
}
/**
* AE date/time processing is built according to a subset of the standard Java
* SimpleDateFormat patterns.  You can find this at:
* http://java.sun.com/j2se/1.4.2/docs/api/java/text/SimpleDateFormat.html
* We allow the following symbols: y,M,d for dates and h,H,m,a for time.  We
* do not support any B.C. times.  We do not support any patterns that have no
* non-digit delimiters (like MMddyyyy).  We do not support G,w,W,D,F,E,k,
* K,S,z,Z.
*/

/**
* Converts a date or datetime string to a JavaScript Date object using
* locale-determined i18n settings.  Will attempt to cover for strings that are
* in an invalid format.  The seconds are ignored.  The day was accidentally
* accomodated.
*
* @param string The string to be converted
* @return A Date object
* @since 4.1SP1
*/
function stringToDate(string, isTime){
  var date = new Date();
  var month;
  date.setHours(0);
  date.setMinutes(0);
  date.setSeconds(0);
  try {
    var twoDigitYearMess = false;
    string = string.split(',')[0].trim();
    if(!isTime) string = parser(string, date, true).trim();
    try { if(string.length) parser(string, date) } catch (e) { }
    if(twoDigitYearMess !== false){
      var thisYear = (new Date()).getFullYear();
      var lowerBound = new Date();
        lowerBound.setYear(thisYear - 80);
      var upperBound = new Date();
        upperBound.setYear(thisYear + 20);
      var testYear = (Math.floor(thisYear/100) - 1)*100 + Number(twoDigitYearMess);
      do {
        date.setYear(testYear);
        testYear += 100;
      } while (date < lowerBound || date >= upperBound)
    }
    if(month == 1 && date.getMonth() == 2 && date.getDate() == 1){ //This clause needed for Feb 29 on leap years
      date.setMonth(1);
      date.setDate(29);
    }
    return date;
  } catch (e) {
    return date;
  }
  function parser(string, date, isDate){
    var buffer = isDate ? datePickerPattern : timePickerPattern + ' a';
    var tokens = isDate ? '^y+|^M+|^d+' : '^h+|^H+|^m+|^s+|^a+';
    while(buffer.length != 0){
      var match = buffer.match(new RegExp(tokens));
      if(!match) {
        buffer = buffer.slice(1);
        string = string.replace(/^[^a-zA-Z0-9]*/, '');
      } else {
        buffer = buffer.slice(match[0].length);
        var topChar = match[0].charAt(0);
        var count = match[0].length;
        var stringLength = count;
        switch(topChar){
          case 'y' :
            var year = string.match(/^\d+/)[0];
            stringLength = year.length;
            if(stringLength == 2) twoDigitYearMess = year;
            else date.setYear(year);
            break;
          case 'M' :
            var monthMatch = string.match(/^\d+/);
            if(monthMatch){
              stringLength = monthMatch[0].length;
              month = cap(monthMatch[0], 12) - 1;
            } else {
              monthMatch = string.match(/^[a-zA-Z]+/)[0];
              monthMatch = monthMatch.substr(0,1).toUpperCase() + monthMatch.substr(1).toLowerCase();
              month = longMonths.indexOf(monthMatch);
              if(month == -1)
                month = shortMonths.indexOf(monthMatch);
              if(month == -1) throw new Error('Invalid month entered by the user');
              stringLength = monthMatch.length;
            }
            date.setMonth(month);
            break;
          case 'd' :
            var day = string.match(/^\d+/)[0];
            stringLength = day.length;
            date.setDate(cap(day, 31));
            break;
          case 'h' :
          case 'H' :
            var hour = string.match(/^\d+/)[0];
            stringLength = hour.length;
            date.setHours(cap(hour, 23, true));
            break;
          case 'm' :
            var minute = string.match(/^\d+/);
            stringLength = minute ? minute[0].length : 0;
            date.setMinutes(minute ? cap(minute[0], 59, true) : 0);
            break;
          case 's' :
            var second = string.match(/^\d+/);
            stringLength = second ? second[0].length : 0;
            date.setSeconds(second ? cap(second[0], 59, true) : 0);
            break;
          case 'a' :
            var ampm = string.match(/[^ap]*[ap]m?/i)[0];
            stringLength = ampm.length;
            var hours = date.getHours();
              if(hours == 12) { hours = 0; date.setHours(0) };
            if(ampm.match(/p/i) && hours < 12) date.setHours(hours + 12);
            break;
        }
        string = string.slice(stringLength);
      }
    }
    return string;
  }
  function cap(str, cap, allowZero){
    num = Number(str);
    if(!allowZero && num == 0) return 1;
    else if(num <= cap) return num;
    else if(allowZero && num == cap + 1) return 0;
    do { str = str.slice(0, -1)
    } while (Number(str) > cap);
    return Number(str);
  }
}
/**
* Validates a string as a date/datetime against the user's locale.  Understands
* any of the symbols [yMdhHmsa].
*
* @param string The string to be validated
* @param type   'date', 'time', or 'datetime'
* @return A Date object
* @since 4.1SP1
*/
function validateDatetime(string, type){
  if(typeof dateRegEx == 'undefined' || typeof timeRegEx == 'undefined' || dateRegEx == '' || timeRegEx == ''){
    dateRegEx = datePickerPattern.escapeRegEx();
      dateRegEx = dateRegEx.replace(/y+/, '\\$2{1,4}');
      dateRegEx = dateRegEx.replace(/MMMM/, '(' + longMonths.join('|').replace(/M/g, '$1') + ')');
      dateRegEx = dateRegEx.replace(/MMM/, '[A-Z][a-z][a-z]');
      dateRegEx = dateRegEx.replace(/MM/, '(0[1-9]|1[0-2])');
      dateRegEx = dateRegEx.replace(/M/, '(1[0-2]|[1-9])');
      dateRegEx = dateRegEx.replace(/dd/, '(0[1-9]|[12][0-9]|3[01])');
      dateRegEx = dateRegEx.replace(/d/, '([12][0-9]|3[01]|[1-9])');
      dateRegEx = dateRegEx.replace(/\$1/g, 'M');
      dateRegEx = dateRegEx.replace(/\$2/g, 'd');
    timeRegEx = timePickerPattern.escapeRegEx();
      timeRegEx = timeRegEx.replace(/hh/, '(0[1-9]|1[0-2])');
      timeRegEx = timeRegEx.replace(/h/, '(1[0-2]|[1-9])');
      timeRegEx = timeRegEx.replace(/HH/, '([01][0-9]|2[0-3])');
      timeRegEx = timeRegEx.replace(/H/, '(1[0-9]|2[0-3]|[0-9])');
      timeRegEx = timeRegEx.replace(/mm/, '[0-5][0-9]');
      timeRegEx = timeRegEx.replace(/m/, '[1-5]?[0-9]');
      timeRegEx = timeRegEx.replace(/ss/, '[0-5][0-9]');
      timeRegEx = timeRegEx.replace(/s/, '[1-5]?[0-9]');
      timeRegEx = timeRegEx.replace(/aa/, '[AP]M');
      timeRegEx = timeRegEx.replace(/a/, '[AP]M');
  }
  var regex = '^';
    if(type == 'datetime' || type == 'date') regex += dateRegEx;
    if(type == 'datetime') regex += ' ';
    if(type == 'datetime' || type == 'time') regex += timeRegEx;
    regex += '$';
  return string.match(new RegExp(regex)) != null;
}
/**
* Formats a date into a string using a locale-defined mask (you can find it in
* include_js.jsp).
* 
* @param type    Whether you want date and/or time in the string.  Acceptable
*                values are 'date', 'datetime', or 'time'.
* @since 4.1SP1
*/
Date.prototype.toAString = function (type) {
  var self = this;
  if(type == 'datetime') var mask = datePickerPattern + ' ' + timePickerPattern;
  else var mask = type == 'date' ? datePickerPattern : timePickerPattern;

  var tokens = 'yMdhHmsa'.split('');
  for(var i=0; i<tokens.length; i++){
    var regex = new RegExp('(' + tokens[i] + '+)');
    if(regex.test(mask)) mask = mask.replace(RegExp.$1, '{' + RegExp.$1 + '}');
  }
  var match;
  do{
    match = mask.match(/\{[^}]*\}/);
    if(match) mask = mask.replace(match[0], tokenTranslator(match[0].slice(1, -1)));
  } while(match)

  return mask;

  function tokenTranslator(token){
    var month   = self.getMonth();
    var date    = self.getDate();
    var hours   = self.getHours();
    var sHours  = (11 + hours)%12 + 1;
    var minutes = self.getMinutes();
    var seconds = self.getSeconds();

    switch(token){
      case 'y':
      case 'yy':   return shortYear(self);
      case 'yyyy': return self.getFullYear();
      case 'M':    return month + 1;
      case 'MM':   return (month + 1 < 10) ? '0' + (month + 1) : month + 1;
      case 'MMM':  return toText(month);
      case 'MMMM': return toText(month, true);
      case 'd':    return date;
      case 'dd':   return (date < 10) ? '0' + date : date;
      case 'h':    return sHours;
      case 'hh':   return (sHours < 10) ? '0' + sHours : sHours;
      case 'H':    return hours;
      case 'HH':   return (hours < 10) ? '0' + hours : hours;  
      case 'm':    return minutes;
      case 'mm':   return (minutes < 10) ? '0' + minutes : minutes;
      case 's':    return seconds;
      case 'ss':   return (seconds < 10) ? '0' + seconds : seconds;
      case 'a':
      case 'aa':   return (hours < 12) ?  'AM' : 'PM';
    }
  }
  function toText(month, isLong){
    var array = isLong ? longMonths : shortMonths;
    return array[month];
  }
  function shortYear(date){
    var thisYear = (new Date()).getFullYear();
    var lowerBound = new Date();
      lowerBound.setYear(thisYear - 80);
    var upperBound = new Date();
      upperBound.setYear(thisYear + 20);
    return (date < lowerBound || date >= upperBound) ? date.getFullYear() : date.getFullYear().toString().substr(2, 2);
  }
}
/**
* Automatically corrects a date to obey the date/time mask
*
* @param field      The field containing the string that represents a date/time.
* @param type       Valid values are 'date', 'time', and 'datetime'.
* @param noMultiple Boolean.  Set to true to force autoCorrect to output a
*                   single date/time value, despite commas.
* @since 4.1SP1
*/
function autoCorrect(field, type, noMultiple){
  var value = field.value;
  if(value == '' || value.validate(type, !noMultiple)) return null;
  var array = noMultiple ? [value] : value.split(',');
  value = '';
  for (var i=0; i<array.length; i++){
    value += stringToDate(array[i], type=='time').toAString(type);
    if(i+1 != array.length) value += ', ';
  }
  field.value = value;
}
//Functions here are designed for broad re-use, but they all have contextual
//requirements that are only satisfied in parts of the application.
function getDetailPage(type, id) {
  var detailPane = getObject('detailPane');
  if (type == TYPE_TASK) detailPane = getObject('dTaskView');
  if (detailPane && type != TYPE_PROCESSMODEL_FOLDER) {
    detailPane.innerHTML = asi.WAIT;
    detailPane.childNodes[0].style.height = '500px';
    detailPane.childNodes[0].style.backgroundColor = 'white';      
  }
  switch (type) {
    case TYPE_TASK:
      backgroundPage('/process/getTaskDetails.do?$e=dTaskView&taskId='+id);
      break;
    case TYPE_PROCESS_MODEL:
      backgroundPage('/process/getprocessmodeldetail.do?$e=detailPane&processModelId='+id+'&folder_id='+arguments[2]);
      break;
    case TYPE_PROCESSMODEL_FOLDER:
       backgroundPage('/process/getProcessModelFolders.do?$e=portalContent&id='+id+'&friendly='+arguments[2]);
       break;
    case TYPE_GROUP:
       backgroundPage('/personalization/groupdetail.do?$e=detailPane&gid='+id);
       break;
    case TYPE_USER:
       backgroundPage('/personalization/userdetail.do?$e=detailPane&un='+id);
       break;
  }
}
/*
* One of three functions to dynamically resize the detail pane.
*/
function pickDetailPane(obj, evt) {
  obj.className = obj.className.replace(/ *moving/g, '');
  obj.className += " moving";
    evt = evt ? evt : event ? event : null;
    if (evt == null) return false;
    window.dragRefY = evt.pageY ? evt.pageY : event.clientY + document.body.scrollTop;
    window.moveTarget = obj;
}
/*
* One of three functions to dynamically resize the detail pane.
*/
function moveDetailPane(gridId, evt) {
  if (!window.moveTarget) return false;
  var obj = window.moveTarget;
  evt = evt ? evt : event ? event : null;
  if (evt == null) return false;    
  if (obj.className.match(/moving/) && (evt.button == 1|| evt.which == 19)) {
    var y = evt.pageY ? evt.pageY : event.clientY + document.body.scrollTop;
    var obj = getObject(gridId);
    var height = "currentStyle" in obj ? obj.currentStyle.height : getComputedStyle(obj, null).height;
    height = parseInt(height) + y - window.dragRefY;
    if (height > 20) getObject(gridId).style.height = height + "px";
    getObject(gridId).style.overflow="auto";
    window.dragRefY = y;
    unselectDocument();
    document.body.style.cursor = 'n-resize';
  }
}
/*
* One of three functions to dynamically resize the detail pane.
*/
function dropDetailPane() {
  if (!window.moveTarget) return false;
  var obj = window.moveTarget;
  obj.className = obj.className.replace(/ *moving/g, '');
  document.body.style.cursor = 'default';
}
/**
* Minimize or maximize detail pane.
*/
function resizeDetailPane(action) {
  var container = getObject('portalContent');
  if (!container) return false;
  container.className = container.className.replace(/( *maximized)|( *minimized)/g, '');
  switch (action) {
      case PANE_MINIMIZE :
          container.className += ' minimized';
          break;
      case PANE_MAXIMIZE :
          container.className += 'maximized';
          if (iframeForm = getObject('internalFormIframe')) {
            window.internalFormIframeHeight = iframeForm.style.height;
            iframeForm.style.height = (getObject('mainContent').offsetHeight - 180) + 'px';
          }
          break;
      case PANE_RESTORE :
          if (iframeForm = getObject('internalFormIframe')) {
            iframeForm.style.height = internalFormIframeHeight;
          }
        break;
  }
}if (typeof HTMLElement != 'undefined') {
  try{
    /* ===== MOZILLA COMPATIBILITY FUNCTIONS ===== */
    with (HTMLElement.prototype) {  
      // support "parentElement"     
      __defineGetter__("parentElement", function () {
        return (this.parentNode == this.ownerDocument) ? null : this.parentNode;
        });
      // mimic microsoft's "currentStyle"     
      __defineGetter__("currentStyle", function () {
        return getComputedStyle(this, null);
        });
      // mimic microsoft's "runtimeStyle"     
      __defineGetter__("runtimeStyle", function () {
        return this.ownerDocument.defaultView.getOverrideStyle(this, null);
        });
      // support "innerText"     
      __defineGetter__("innerText", function () {
        return this.textContent;
        });
      __defineSetter__("innerText", function (value) {
          while (this.hasChildNodes()) this.removeChild(this.lastChild);
          this.appendChild(this.ownerDocument.createTextNode(value));
      });     
    }

    HTMLElement.prototype.swapNode = function (node) {
      this.parentNode.replaceChild(node, this);
      return this;
    }
  } catch(e){}
}
  if (typeof Node != 'undefined') {
      try{
   with (Node.prototype) {
     // support document.all
     __defineGetter__('all', function () {
       if (document.getElementsByTagName('*').length) {
         switch (this.nodeType) {
           case 9 :
             return document.getElementsByTagName('*');
           break;
           case 1 :
             return this.getElementsByTagName('*');
           break;
         }
       }
       return ' '; 
     });
     __defineSetter__('all', function(){});
   }
      }catch(e){}
  }
   var isSafari = navigator.userAgent.indexOf('Safari') > -1;function togglePortletAdmin(pid){
  adminId = "asiAdmin" + pid;
  titleId = "tdportlettitle" + pid;
  linkId = "tdportletoption" + pid;
  adminObject = getObject(adminId,"1");
  titleObject = getObject(titleId,"0");
  linkObject = getObject(linkId,"0");
  if(adminObject["display"] == "block"){
    adminObject["display"] = "none";
    if(titleObject!=null)
      titleObject.className = "tdPortletTitle";
    if(linkObject!=null)
      linkObject.className = "tdPortletTitleTab";
  }
  else{
    adminObject["display"] = "block";
    if(titleObject!=null)
      titleObject.className = "tdPortletTitleInactive";
    if(linkObject!=null)
      linkObject.className = "tdPortletTitleTabActive";
  }
}
function deleteBookmark (id) {
  document.frmDeleteBookmark.$p.value = id;
  document.frmDeleteBookmark.submit(); 
}
function showNotification(id,page){
  var strUrl= strNotifActionPath + "?id="+id+"&r="+escape(page);
  notifWin=popup(strUrl,'notifWin','width=600,height=400,scrollbars=yes,resizable=yes');
}
//Functions that should be moved to an application-specific js file, or reduced to zero-usage and deleted//
function openExtLink(pageURL){
    targetURL = linkhandlerPath+"?url=" + escape(pageURL);
    window.open(targetURL, 'windowname','toolbar=1,menubar,top=50,left=200, width=500,height=400,screenX=200,screenY=50,status,location,resizable=yes,scrollbars=yes');
}
function setObjContents(objId,contentStr){
  getObject(objId).innerHTML = contentStr;
}
function toggleArrow(arrowId){
  if((document.getElementById(arrowId).src).indexOf(asiArrowUpURL)>0){
    document.getElementById(arrowId).src = asiArrowDownURL;
  }
  else{
    document.getElementById(arrowId).src = asiArrowUpURL;
  }
}
function validatePortalSearch(searchValue){
  var searchTerm = trim(searchValue);
  if(searchTerm=="") {
   asi.alert("Please enter a term to search for.");
   return false;
  }
  else if(searchTerm.match(/^[\*\?]/)) {
	asi.alert("Search term cannot start with * or ?");
	return false;
  } else if ( "-" == searchTerm ) {
  	asi.alert("'-' is not a valid search term.");
	  return false;    
  } else if(searchTerm.match(/[\'\"]/)) {
	asi.alert("Search term cannot contain quotes. Please enter a new term to search for.");
	return false;
  }
  else{
    return true;
  }
}
function ACGetDocument(id, inline, kcid){ 

    
	if(isAnonymous){ // variable to determine anonymous user (set in constants.jsp)
	   document.location.href= CONTEXT_PREFIX + "doc/"+id;
    } else {
		if(!inline)
			  inline = 'false';
		  else if(inline == 'inline')
			inline = 'true';

		  if(kcid){
			window.frames['fProcess'].location.href = strACDocumentExternalPath + "?id=" + id + "&inline=" + inline+ "&kcid=" + kcid;    
		  }
		  else{
			window.frames['fProcess'].location.href = strACDocumentExternalPath + "?id=" + id + "&inline=" + inline;
		  }
	}
}

function ACGetDocumentVersion(id, vid, inline, kcid){ 

    
	if(isAnonymous){ // variable to determine anonymous user (set in constants.jsp)
	   // william.blackwell : No idea what to do here.
    } else {
		if(!inline)
			  inline = 'false';
		  else if(inline == 'inline')
			inline = 'true';

		  if(kcid){
			window.frames['fProcess'].location.href = strACDocumentExternalPath + "?id=" + id + "&inline=" + inline+ "&kcid=" + kcid + "&inline=" + inline + "&vid=" + vid;    
		  }
		  else{
			window.frames['fProcess'].location.href = strACDocumentExternalPath + "?id=" + id + "&inline=" + inline + "&vid=" + vid;
		  }
	}
}
function doKey(e) {
	var pK;
	var tN;
	if(window.event){
		pK=window.event.keyCode;
		tN=window.event.srcElement.tagName;
	}
	else{
		pK=e.which;
		tN=e.target.tagName;
	}	
	if (pK==13&&tN.toUpperCase()!='TEXTAREA') return false;
  else if (pK==27)try{DIALOG.hideAll()}catch(e){}
}

//Functions that are not used and should be deleted as soon as all branches are merged//
function viewSrc() {
     objWin=window.open("","objWin","top=5,left=5,width=1240,height=920,scrollbars=yes,resizable=yes");objDoc=objWin.document;objDoc.open("text/plain");objDoc.write(document.documentElement.outerHTML);objDoc.close();
}
function resizeIframe(o,offset) {
  
  if (window.innerHeight)  {
    theHeight = window.innerHeight
  } else if (document.documentElement && document.documentElement.clientHeight)   {
    theHeight = document.documentElement.clientHeight
  } else if (document.body) {
    theHeight = document.body.clientHeight
  }

  theHeight -= offset;
  theHeight /= 2;

  if (theHeight > 0) {
  	o.style.height = theHeight + "px";
    if (o.name != "hContent")
      o.style.display = "inline";
  } else {
    o.style.display = "none";
  }
}
function getTopX() {
  if (window.innerHeight)
  {
    theHeight = window.innerHeight
  }
  else if (document.documentElement && document.documentElement.clientHeight)
  {
    theHeight = document.documentElement.clientHeight
  }
  else if (document.body)
  {
    theHeight = document.body.clientHeight
  }
  return theHeight;
}
function getRightY() {
  if (window.innerWidth)
  {
    theWidth = window.innerWidth
  }
  else if (document.documentElement && document.documentElement.clientWidth)
  {
    theWidth = document.documentElement.clientWidth
  }
  else if (document.body)
  {
    theWidth = document.body.clientWidth
  }
  return theWidth;
}
function setTop(o,x) {
  if (x>=0)
    o.style.top = x+"px";
}
function setHeight(o,x) {
  if (x>0)
    o.style.height = x+"px";
}

function getCenterX() {
  return getTopX() / 2;
}

function getCenterY() {
  if (window.innerWidth)
  {
    theWidth = window.innerWidth
  }
  else if (document.documentElement && document.documentElement.clientWidth)
  {
    theWidth = document.documentElement.clientWidth
  }
  else if (document.body)
  {
    theWidth = document.body.clientWidth
  }
  theWidth /= 2;
  return theWidth;
}
function openEmailToGroup( groupId, evt ) {
	window.open('../toemailgroup.do?gid=' + groupId,'','left=200,width=750,height=275,scrollbars=no');
  evt = evt ? evt : event ? event : null;
  if(evt) event.cancelBubble = true;
}
function JSEscape(string) {
  if(string){
    string = string.replace(/\\/g,"\\\\");
    string = string.replace(/'/g,"\\'");
    string = string.replace(/"/g,'\\"');
  }
  return string;
}
function HTMLEscape(string) {
  if(string){
    string = string.replace(/</g,"&lt;");
    string = string.replace(/>/g,"&gt;");
    string = string.replace(/'/g,"&#39;");
    string = string.replace(/"/g,'&quot;');
  }
  return string;
}
//Constants that are not used and should immediately be deleted
bShowAdmin = false;


var TYPES=new Array();
var ENTITIES=new Array();

function GroupRule(n){
  this.name=n;
  this.expressions=new Array();
  this.curr=-1;
  this.currentExpression=function(){
    with(this)return expressions[curr];
  }
  //===============================
  this.currentConditions=function(){
	with(this)return expressions[curr].conditions;
  }
  //===============================
  this.encode=function(){
    with(this){
	  validateCurrentExpression();
      var r="";
      for(i=0;i<expressions.length-1;i++){
		r+=expressions[i].encode()+"&";
	  }
	  if(expressions.length>0)r+=expressions[i].encode();
	  r+="";
      return r;
    }
  }
  this.encode_et=function(){
    with(this){
	  validateCurrentExpression();
	  var et="et=";
	  for(i=0;i<expressions.length;i++){
		  if(expressions[i].entityType.label=="u"){
			et+=";"+expressions[i].entityType.label;
		  }else{
			et+=";g";
		  }
	  }
      return et;
    }
  }
  this.encode_etid=function(){
    with(this){
	  validateCurrentExpression();
	  var etid="etid=";
	  for(i=0;i<expressions.length;i++){	  
		  if(expressions[i].entityType.label=="u"){
			etid+=";-1";
		  }else{
			etid+=";"+expressions[i].entityType.id;
		  }
	  }
      return etid;
    }
  }
  this.encode_c=function(){
    with(this){
	  validateCurrentExpression();
	  var c="c=";
	  this.conditions = new Array();
	  this.condition = new MembershipCondition();
	  for(i=0;i<expressions.length;i++){
	   conditions=expressions[i].conditions;
	   if(expressions[i].conditions.length>0){
		   c+=";";
		   for(var j=0;j<conditions.length;j++){
			   condition=conditions[j];
			   c+=","+condition.attribute.id+","+condition.operator.id+","+condition.value;
		   }
	   }
	  }
      return c;
    }
  }
  this.toString=function(){
    //alert("in rule.toString 1");//rp
    with(this){
      var r="";
      for (var i=0; i<expressions.length; i++){
		  //alert("in rule.toString 2");//rp
        if(i==curr){
			//alert("in rule.toString 3");//rp
          r+=expressions[i].toHTML();
          if(curr<0||expressions[curr].isValid()){
			  //alert("in rule.toString 4");//rp
            r+="&nbsp;/&nbsp;<a href='javascript:rule.addExpression();renderRule();'>done</a>)";
          }
          r+="<br>";
        }else{
			//alert("in rule.toString 5-  about to go to expression.toString...");//rp
          r+=expressions[i].toString();
          r+="&nbsp;(<a href='javascript:rule.editExpression("+i+");renderRule();'>edit</a>&nbsp;/&nbsp;";
          r+="<a href='javascript:rule.deleteExpression("+i+");renderRule();'>delete</a>)";
          r+="<br /><br />";
        }
      }
      return r;
    }
  }
  this.addExpression=function(){
    with(this){
      validateCurrentExpression();
      curr=expressions.length;
      expressions.push(new MembershipExpression());
    }
  }
  this.editExpression=function(i){
	//alert("in editExpression");
    with(this){
      validateCurrentExpression();
      curr=i;
    }
  }
  this.validateCurrentExpression=function(){
    //alert("in validateCurrentExpression");
    with(this){
      if(!(curr<0)){
        if(expressions[curr].isValid()){
			//alert("in validateCurrentExpression 1");
          saveCurrentExpression();
        }else{
			//alert("in validateCurrentExpression 2");
          deleteCurrentExpression();
        }
      }
    }
  }
  this.deleteCurrentExpression=function(){
	  //alert("in deleteCurrentExpression");
    with(this){
      expressions.splice(curr,1);
      curr=-1;
    }
  }
  this.deleteExpression=function(i){
    with(this){
      expressions.splice(i,1);
      if(curr==i)curr=-1;
      if(curr>i)curr-=1;
    }
  }
  this.saveCurrentExpression=function(){
    with(this){
      expressions[curr].saveCurrentCondition();
    }
  }
}

function MembershipExpression(){
  this.entityType=null;
  this.conditions=new Array();
  this.curr=-1;
  this.isValid=function(){
    return(this.entityType!=null)&&(this.entityType!=-1);
  }
  this.toHTML=function(){
	//alert("in toHTML");
    with(this){
      var r="add ";
      if(entityType==null){
		  //alert("in toHTML 1");
        r+="<a href='javascript:userClicked();'>users</a>";
        r+="&nbsp;/&nbsp;";
        r+="<a href='javascript:groupClicked();'>groups</a>";
      }else if(entityType==-1){
        r+="<b>groups</b> of type ";
        r+="<select name='selGrpType' onchange='groupPicked(this[this.selectedIndex].value);'>";
        r+="<option selected value=''>&lt;choose a type&gt;</option>";
        for (var e=0; e<ENTITIES.length; e++){
          if(e!="u")r+="<option value='"+e+"'>"+ENTITIES[e].label+"</option>";
        }
        r+="</select>";
      }else{
		//alert("in toHTML 2");
        if(conditions.length==0){
		  //alert("in toHTML 2.1");
          conditions.push(new MembershipCondition());
          curr=0;
        }
        if(entityType==ENTITIES["u"]){
		  //alert("in toHTML 2.2");
          r+="<b>users</b> whose ";
        }else{
		  //alert("in toHTML 2.3");
          r+="<b>groups</b> of type \"<b>"+entityType.label+"</b>\" for which ";
        }
        for(var i=0;i<conditions.length;i++){
          if(i==curr){
			//alert("in toHTML 2.4");//goes here when editing a condition you just added
            if(conditions.length>1)r+="(<a href='javascript:rule.currentExpression().deleteCurrentCondition();renderRule();'>delete</a>)";
            r+=conditions[i].toHTML(entityType.attributes);
          }else{
			//alert("in toHTML 2.5");//goes here when editing a condition you saved
            r+="<a href='javascript:rule.currentExpression().selectCondition("+i+");renderRule();'>"+conditions[i].toString()+"</a>";
          }
          if(i!=conditions.length-1)r+=", and ";
        }
        if(curr>=0)r+=" (<a href='javascript:rule.currentExpression().addCondition();renderRule();' title='Add another condition'>more</a>";
      }
      return r;
    }
  }
  this.encode=function(){
    with(this){
      var r="";
	  if (entityType.label=="u"){
		  r="[({utid,"+entityType.id+"}),"+"(";
	  }else{
		  r="[({gtid,"+entityType.id+"}),"+"(";
	  }
      for(var i=0;i<conditions.length-1;i++){
        r+=conditions[i].encode();
      }
	  if(conditions.length>0)r+=conditions[i].encode()+")";
	  r+="]";
      return r;
    }
  }
  this.toString=function(){
	  //alert("in expression.toString...");//rp
    with(this){
      var r="add ";
	  //alert("in expression.toString...1");//rp
      if(entityType==ENTITIES["u"]){
		  //alert("in expression.toString...entityType==ENTITIES[u]");//rp
        r+="<b>users</b> whose ";
      }else{
		  //alert("in expression.toString...ELSE");//rp
        r+="<b>groups</b> of type \"<b>"+entityType.label+"</b>\" for which ";
      }
      for(var i=0;i<conditions.length;i++){
		 // alert("in expression.toString...4, about to enter conditions.toString()");//rp
        r+=conditions[i].toString();
		if(i!=conditions.length-1)r+=", and ";
		   
      }
      return r;
	  //alert("exiting expression.toString");//rp
    }
  }
  this.deleteCurrentCondition=function(){
    with(this){
      conditions.splice(curr,1);
      curr=Math.max(0,curr-1);
    }
  }
  this.changeCurrentAttribute=function(i){
    with(this){
      conditions[curr].attribute=entityType.attributes[i];
      renderRule();
    }
  }
  this.saveCurrentCondition=function(){
	  //alert("in saveCurrentCondition");
	if(this.curr<0){
		//alert("in saveCurrentCondition 0, and curr = "+this.curr);
		return;
	}
    with(this.conditions[this.curr]){
      if(attribute.type.operators.length>1){
		//alert("in saveCurrentCondition 1");
		operator=attribute.type.operators[document.agRuleForm.selOper.selectedIndex];
	  }
      else {
		  //alert("in saveCurrentCondition 2");
		  operator=attribute.type.operators[0];
	  }
      value=document.agRuleForm.attrVal.value;
    }
  }
  this.addCondition=function(){
    with(this){
      saveCurrentCondition();
      conditions.push(new MembershipCondition());
      curr=conditions.length-1;
    }
    renderRule();
  }
  this.selectCondition=function(i){
	  //alert("in selectCondition");
    with(this){
	  saveCurrentCondition();
      curr=i;
	  renderRule();
    }
  }
}

function MembershipCondition(){
  this.attribute=null;
  this.operator=null;
  this.value=null;
  this.toHTML=function(a){
	//alert("in Condition.toHTML");
    with(this){
      if(attribute==null){
		  //alert("attribute was NULL");
		  attribute=a[0];
	  }

		//test
		//alert("this.value = "+value);
		//alert("this.operator.id = "+operator.id);
		//alert("this.operator.label = "+operator.label);
		//alert("this.attribute.id = "+attribute.id);
		//alert("this.attribute.label = "+attribute.label);
		//end test

      return attributesToHTML(a)+attribute.type.operatorToHTML(operator)+attribute.type.valueToHTML(value);
    }
  }
  this.attributesToHTML=function(a){
    with(this){

	  //test
	  //alert("attribute.id = "+attribute.id);
	  //alert("attribute.label = "+attribute.label);
	  //end test

      var r="<select name='selAttr' onchange='rule.currentExpression().changeCurrentAttribute(this.selectedIndex);renderRule();'>";
      for (var i=0; i<a.length; i++){
        r+="<option ";
        if(a[i].id==attribute.id){
			r+="selected ";
		}
        r+="value='"+a[i].id+"'>"+a[i].label+"</option>";
      }
      r+="</select>";
      return r;
    }
  }
  this.toString=function(){
	  //alert("in condition.toString");//rp
    with(this){
		//alert("in condition.toString 1");//rp
      var r="<i>"+attribute.label+"</i>";
	    //alert("operator="+operator.label);//rp
	    //alert("in condition.toString 2");//rp
		//alert("value="+value);//rp
      r+=" "+attribute.type.operatorToString(operator)+" ";
	  //r+="is";
	   //alert("in condition.toString 3");//rp
      r+="<b> "+attribute.type.valueToString(value)+"</b>";
	  //r+="crappy"; 
	   //alert("in condition.toString 4");//rp
      return r;
    }
	 //alert("exiting condition.toString");//rp
  }
  this.encode=function(){
    with(this){
      var r="{"+attribute.id+",";
      r+=operator.id+",";
      r+=value+"}";
      return r;
    }
  }
}

function MembershipType(d){
  this.domain=d;
  this.operators=new Array();
  this.valueToHTML=function(v){
    with(this){
      var r="";
      if(domain==null){
        r+="<input name='attrVal' type='text' value='"+escapeText(v==null?"":v)+"'>";
      }else{
        r+="<select name='attrVal'>";
        for (var i=0; i<domain.length; i++){
          r+="<option value='"+i+"'";
          if(v==i)r+=" selected";
          r+=">"+domain[i]+"</option>";
        }
        r+="</select>";
      }
      return r;
    }
  }
  this.valueToString=function(v){
    with(this){
      var r="";
      if(domain==null){
        if(v=="")r+="[empty]";
        else r+=v;
      }else r+=domain[v];
      return r;
    }
  }
  this.operatorToHTML=function(o){
    with(this){
      if(operators.length>1){
        var r="<select name='selOper'>";
        for (var i=0; i<operators.length; i++){
          r+="<option value='"+operators[i].id+"'";

		  //new
		  if(o!=null){
			  if(o.id==operators[i].id)r+=" selected";
		  }else{
			  if(o==operators[i])r+=" selected";
		  }

          //if(o==operators[i])r+=" selected";
          //if(o.id==operators[i].id)r+=" selected";
		  r+=">"+operators[i].label+"</option>";
        }
        r+="</select>";
        return r;
      }else{
        return " "+operators[0].label+" ";
      }
    }
  }
  this.operatorToString=function(o){
    return o.label;
  }
}

function MembershipOperator(i,l){
  this.id=i;
  this.label=l;
}

function MembershipAttribute(i,l,t){
  this.id=i;
  this.label=l;
  this.type=t;
}

function Entity(i,l){
  this.id=i;
  this.label=l;
  this.attributes=new Array();
}


//global functions (probably due on the page)
function userClicked(){
  rule.currentExpression().entityType=ENTITIES["u"];
  renderRule();
}
function groupClicked(){
  rule.currentExpression().entityType=-1;
  renderRule();
}
function groupPicked(g){
  rule.currentExpression().entityType=ENTITIES[g];
  renderRule();
}

function renderRule(){
  rb=getObject("rulesdiv",0);
  rb.innerHTML=rule.toString();
}
function escapeText(str){
  expr=/([\'])/gm;
  return str.replace(expr,"&#0039;");
}
/* Deprecated file: no additions */
if(!window.asi) window.asi = new Object();

asi.WAIT = "<div class='asiWait'>Please Wait...</div>";
 /* Deprecated constants: use from /framework/js/constants.jsp */
asi.TYPES = new Object();
asi.TYPES.DEFERRED = TYPE_DEFERRED;
asi.TYPES.LONG = TYPE_LONG;
asi.TYPES.DOUBLE = TYPE_DOUBLE;
asi.TYPES.STRING = TYPE_STRING;
asi.TYPES.USER = TYPE_USER;
asi.TYPES.GROUP = TYPE_GROUP;
asi.TYPES.CURRENCY = TYPE_CURRENCY;
asi.TYPES.DATE = TYPE_DATE;
asi.TYPES.TIME = TYPE_TIME;
asi.TYPES.DATETIME = TYPE_DATETIME;
asi.TYPES.BINARY = 10;
asi.TYPES.BEAN = TYPE_BEAN;
asi.TYPES.FOLDER = TYPE_FOLDER;
asi.TYPES.DOCUMENT = TYPE_DOCUMENT;
asi.TYPES.PAGE      = TYPE_PAGE;
asi.TYPES.FORUM     = TYPE_FORUM;
asi.TYPES.DISCUSSION_THREAD = TYPE_DISCUSSION_THREAD;
asi.TYPES.MESSAGE   = TYPE_MESSAGE;
asi.TYPES.KNOWLEDGE_CENTER  = TYPE_KNOWLEDGE_CENTER;
asi.TYPES.COMMUNITY = TYPE_COMMUNITY;
asi.TYPES.TASK      = TYPE_TASK;
asi.TYPES.PROCESS   = TYPE_PROCESS;
asi.TYPES.TEMPLATE  = TYPE_TEMPLATE;
asi.TYPES.ATTACHMENT = TYPE_ATTACHMENT;
asi.TYPES.ROLE      = TYPE_ROLE;
asi.TYPES.PROCESSMODEL_FOLDER = TYPE_PROCESSMODEL_FOLDER;
asi.TYPES.SIM_SCENARIO = TYPE_SIM_SCENARIO;
asi.TYPES.PASSWORD = TYPE_PASSWORD;
  
asi.TYPES.BOOLEAN   = 26;
asi.TYPES.PEOPLE = 27;
asi.TYPES.CONTENT = 28;

asi.TYPES.ARRAY_INCREMENT = 1000;

asi.parameters = new Array();
asi.getParameters = function (id) {
  var params = asi.parameters[id];
  if (params) return params;
  if (id == 'asiWizard') return new asi.WizardParameters();
  if (id == 'asiPicker') return new asi.PickerParameters();
  if (id == 'parent.asiPicker') return new asi.PickerParameters();
}
importStyleSheet('/public/components/dialogs/css/asiDialogs.css');

/*********************************
 FORMS
*********************************/
importStyleSheet('/public/components/form/css/asiForm.css');

function getFieldLegend(field) {
  var fieldset = getContainerByTagName(field, 'FIELDSET');
  if (fieldset) return fieldset.getElementsByTagName('LEGEND')[0];
  else return null;
}
function getFieldLabel(field) {
  if (field.parentNode.tagName == 'LABEL') {
    var labelContainer =getElementsByClassName('label', field.parentNode, 'SPAN')[0];
    var labelText;
    if (labelContainer) {
      labelText = labelContainer.innerText;
      if(!labelText) labelText = labelContainer.innerHTML;
    } else {
      labelText = field.parentNode.innerText;    
      if (!labelText) return field.parentNode.childNodes[0].value;  
    }
    return labelText.replace(/^\s+/, '').replace(/\s\n.*/,'');
  }  else if (field.htmlFor) {
      return getObject(field.htmlFor);
  }
  if (!getFieldLegend(field)) return '';
  var lastresort = getFieldLegend(field).innerText;
  if(!lastresort)
    lastresort = getFieldLegend(field).firstChild.innerHTML;
  return lastresort;
}
function getErrorContainer(field, isGenerateNew) {
  if (field.type == "hidden") {
    return null; 
  }
  var label = getContainerByTagName(field, 'LABEL',4);
  if(!label) return null;
  if(field.type in set('checkbox', 'radio')) label = label.parentNode;
  var containers = getElementsByClassName('errorMessage', label);
  if (isGenerateNew && containers.length == 0 ){
    var span = document.createElement('SPAN');{
      span.className = 'errorMessage';
    }
    label.appendChild(span);
    return span;
  }
  return containers[0];
}
function setFieldError(field, message) {
  var container = getErrorContainer(field, true);
  if (!container) return false;
  try{ container.innerHTML = message; }
  catch(e){ container.innerText = message; }
  return true;
}
function clearAllErrors(form) {
  var a = getElementsByClassName("errorMessage",form);
  for (var i = 0; i < a.length; i++) {
    a[i].innerHTML='';
  }
}
function clearErrors(elements) {
  if (!elements) return;
  var container;
  if (!elements.length) {
    container =  getErrorContainer(elements);
    if(container) container.innerHTML = '';
    return;
  }
  for (var i = 0; i < elements.length; i++) {
    if(isField(elements[i])){
      container = getErrorContainer(elements[i]);
      if(container) container.innerHTML = '';
    }
  }
}
function removeErrorContainer(field) {
  if(field.tagName == 'INPUT') var labelContainer = getContainerByTagName(field, 'LABEL');
  if(!labelContainer) return false;
  if(field.tagName == 'LABEL') var labelContainer = field;
  var errorContainer = getElementsByClassName('errorMessage', labelContainer);
  if(errorContainer.length == 1){
    errorContainer = errorContainer[0];
    errorContainer.parentNode.removeChild(errorContainer);
  }
  return true;
}
function extractFields(fieldset, toHidden) {
  var fieldsetContainer = fieldset.parentNode;
  var fields = toHidden ? fieldset.getElementsByTagName('INPUT') : fieldset.getElementsByTagName('LABEL');
  while (fields.length > 0) {
    var field = fields[0];
    if (field.className == 'booleanInput'){
      if (field.checked) fieldsetContainer.parentNode.insertBefore(normalizeBooleanInput(field) ,fieldsetContainer);
      field.parentNode.removeChild(field);
    }
    else fieldsetContainer.parentNode.insertBefore(field,fieldsetContainer);
  }
  fieldsetContainer.parentNode.removeChild(fieldsetContainer);
}
function normalizeBooleanInput(radio) {
  var label = document.createElement('LABEL');{
    var span = document.createElement('SPAN');{
      span.appendChild(document.createTextNode(getFieldLabel(radio)));
      span.style.display = 'block';
    }
    label.appendChild(span);
    var input = document.createElement('INPUT');{
      input.name = radio.name;
      input.value = getFieldValue(radio);
      input.className = 'iText';
    }
    label.appendChild(input);
  }
  return label;
}
/* This is mainly useful for toggling the required status of pickers */
function setRequiredStatus(submittedTextObject, isRequired){
  var listItem = getContainerByTagName(submittedTextObject, 'LI');
  var inputFields = getInputsByType('text', listItem);
  for(var i=0; i<inputFields.length; i++){ inputFields[i].required = isRequired }
}
/*********************************
 VALIDATION FUNCTIONS
*********************************/
function standardValidate(form) {

  var errors = new Array();
  var errorRefs = new Array();
  var elements = form.elements;
  clearErrors(elements);
  var validate, value, field, label, required, matchWith, validationMessage;
  var ERROR_TRUNCATE = 25;
  for (var i = 0; i < elements.length; i++) {
    field = elements[i];
    if(!isField(field)) continue;
    validate = field.validate || field.getAttribute('validate');
    if(!isVisible(field) && validate != "htmlarea") continue;
    validationMessage = field.validationMessage || field.getAttribute('validationMessage');
    value = getFieldValue(field);
    var realMultiple = !!field.getAttribute('realMultiple');
    var multiple = realMultiple ? field.getAttribute('realMultiple') == 'true' : true;
    if(validate == "htmlarea") value=value.replace(/<img.+?>/gi,'i').replace(/<.+?>/g, '').replace(/&nbsp;/g, ' ');
    if (field.tagName == 'TEXTAREA') {
      var maxlength = field.maxlength || field.getAttribute('maxlength');
      if (maxlength && value.length > maxlength) {
        validationMessage = validationMessage || ("Value exceeds maximum allowed");
        errors.push(validationMessage);
        errorRefs.push(field);          
      }
    }
    if (value != null && trim(value) != '') {
      switch (validate) {
          case "positive" :
              if (!value.validate("positive", multiple)) {
                validationMessage = validationMessage || value.truncate(ERROR_TRUNCATE, true) + (realMultiple && multiple ? " is not a valid set of positive numbers." : " is not a positive number.");
                errors.push(validationMessage);
                errorRefs.push(field);
              }
              break;
          case "long" :
              if (!value.validate("long", multiple)) {
                validationMessage = validationMessage || value.truncate(ERROR_TRUNCATE, true) + (realMultiple && multiple ? " is not a valid set of integers." : " is not an integer.");
                errors.push(validationMessage);
                errorRefs.push(field);
              }
              break;
          case "float" :
          case "scientific" :
              if (!value.validate(validate, multiple)) {
                validationMessage = validationMessage || value.truncate(ERROR_TRUNCATE, true) + (realMultiple && multiple ? " is not a valid set of numbers." : " is not a number.");
                errors.push(validationMessage);
                errorRefs.push(field);
              }
              break;
          case "alphanumeric" :
              if (!value.validate("alphanumeric", multiple)) {
                validationMessage = validationMessage || value.truncate(ERROR_TRUNCATE, true) + (realMultiple && multiple ? " is not a valid set of entries." : " is not a valid entry.") + " Only numbers, letters and underscores are allowed.";
                errors.push(validationMessage);
                errorRefs.push(field);
              }
              break;
          case "date" :
              if (!value.validate("date", multiple)) {
                validationMessage = validationMessage || value.truncate(ERROR_TRUNCATE, true) + (realMultiple && multiple ? " is not a valid set of dates." : " is not a valid date.") + " The valid format is " + datePickerPattern + ".";
                errors.push(validationMessage);
                errorRefs.push(field);
              }
              break;
          case "time" :
              if (!value.validate("time", multiple)) {
                validationMessage = validationMessage || value.truncate(ERROR_TRUNCATE, true) + (realMultiple && multiple ? " is not a valid set of times." : " is not a valid time.") + " The valid format is " + timePickerPattern + ".";
                errors.push(validationMessage);
                errorRefs.push(field);
              }
              break;
          case "datetime" :
              if (!value.validate("datetime", multiple)) {
                validationMessage = validationMessage || value.truncate(ERROR_TRUNCATE, true) + (realMultiple && multiple ? " is not a valid set of datetimes." : " is not a valid date/time.") + " The valid format is " + datePickerPattern + " " + timePickerPattern + ".";
                errors.push(validationMessage);
                errorRefs.push(field);
              }
              break;
          case "zipcode" :
              if (!value.validate("zipcode", multiple)) {
                validationMessage = validationMessage || value.truncate(ERROR_TRUNCATE, true) + (realMultiple && multiple ? " is not a valid set of zipcodes." : " is not a valid zipcode.");
                errors.push(validationMessage);
                errorRefs.push(field);
              }
              break;
          case "ssn" :
              if (!value.validate("ssn", multiple)) {
                validationMessage = validationMessage || value.truncate(ERROR_TRUNCATE, true) + (realMultiple && multiple ? " is not a valid set of SSNs." : " is not a valid SSN.");
                errors.push(validationMessage);
                errorRefs.push(field);
              }
              break;
          case "email" :
              if (!value.validate("email", multiple)) {
                validationMessage = validationMessage || value.truncate(ERROR_TRUNCATE, true) + (realMultiple && multiple ? " is not a valid set of e-mail addresses." : " is not a valid e-mail address.");
                errors.push(validationMessage);
                errorRefs.push(field);
              }
              break;
          case "htmlarea" :
              if (field.value.length >= field.maxlength)
              { 
                validationMessage = validationMessage || ("Message exceeds maximum number of characters.");
                errors.push(validationMessage);
                errorRefs.push(field);
              }
              break;
          case "boolean" :
              if (!value.validate("boolean", multiple)) {
                validationMessage = validationMessage || (realMultiple && multiple ? "Please enter yeses and nos separated by commas." : "Please enter 'yes' or 'no'.");
                errors.push(validationMessage);
                errorRefs.push(field);
              }
              break;
          default:
            if (validate != null && validate.match(/^!?regex\(/)) {
              var isNot = validate.match(/^!/);
              var isCaseInsensitive = validate.match(/\/i\)/);
              var regex = new RegExp(validate.slice(isNot ? 8 : 7, isCaseInsensitive ? -3 : -2), isCaseInsensitive ? 'i' : '');
              if ((!isNot && !value.match(regex)) || (isNot && value.match(regex))) {
                validationMessage = validationMessage || (value.truncate(ERROR_TRUNCATE, true) + " is not a valid entry.");
                errors.push(validationMessage);
                errorRefs.push(field);
              }
            }
            break;
      }
    matchWith = field.matchWith|| field.getAttribute('matchWith');
      if (matchWith) {
        var fields = document.getElementsByName(matchWith);
        if (fields.length > 0) ASI_LOG.warn('Encountered more than one field with the same property name', true);
        var field2 = fields[0];
        if (value != getFieldValue(field2)) {
        errors.push("Value must be the same as " + getFieldLabel(field2));
        errorRefs.push(field);
        errors.push("Value must be the same as " + getFieldLabel(field));
        errorRefs.push(field2);
        }
      }
    }else {
      required = field.required || field.getAttribute('required');
        if (required != null && (required == 'true' || required == 'required')){
          fieldName = trim(getFieldLabel(field).replace(/[:*\r\n]/g, ''));
          var errorText = (field.type in set('checkbox', 'radio') || field.tagName == 'SELECT') ? 'Please pick one of these options.' : fieldName + " cannot be left blank."
          errors.push(errorText);
          errorRefs.push(field);
        }
    }
  }
  if (errors.length > 0) {
    displayErrors(errors, errorRefs);
    return false;
  }
  return true;
}

function displayErrors(errors, errorRefs) {
  var buffer = '';
  for (var i = 0; i < errors.length; i++) {
    if(!setFieldError(errorRefs[i], errors[i])){
      buffer += errors[i] + '<br />';
    }
  }
  if (buffer != '') asi.alert("Required data is missing:<br />" + buffer);
}

function validateSearch(field) {
  var valid = false;
  if (field.length) {
    for (var i=0;i<field.length;i++) {
      if (field[i].value != '' && trim(field[i].value) != ''){
        valid = true;
        break;
      }
    }
  } else {
    if (field.value != '' && trim(field.value) != ''){
      valid = true;
    }
  }
  if (!valid) {
    var message = 'You must fill in at least part of your search criteria. Please refine your search.';
    if (window.name == 'fHeader') window.parent.fBody.asi.alert(message);
    else asi.alert(message);
  }
  return valid;
}

/*********************************
 OBJECT PICKER COMPONENT
*********************************/
importStyleSheet('/public/components/picker/css/asiPicker.css');


asi.WAIT_PICKER = "<iframe name=\"asiPickerBackdrop\" style=\"z-index:-1; position:absolute;top:0;left:0;height:100%;width:100%\"  src=\""+CONTEXT_PREFIX+"portal/blank.jsp\" frameborder=\"0\" title=\"Backdrop frame. No displayed content.\"></iframe><div class='asiWait'>Please Wait...</div>";

TYPE_EXPRESSION = 29;
TYPE_ACTOR = 28; 

asi.PICK_GROUPS_AND_USERS = 1;
asi.PICK_GROUPS = 2;
asi.PICK_USERS = 3;
asi.PICK_FOLDER = 4;
asi.PICK_DOCUMENT = 5;
asi.PICK_ASSIGNEES_AC = 6;
asi.PICK_KCS= 7;
asi.PICK_PORTAL_PAGES = 8;
asi.PICK_ASSIGNEES = 9;
asi.PICK_THREADS = 10;
asi.PICK_FORUMS = 11;
asi.PICK_PROCESS_MODEL = 23;
asi.PICK_WRITABLE_FOLDERS = 13;
asi.PICK_LINK_FOLDERS_PORTLET = 14;
asi.PICK_LINK_FOLDERS_USER = 15;
asi.PICK_LINK_FOLDERS_GROUP = 16;
asi.PICK_DOCUMENT_BY_EXTENSION = 17;
asi.PICK_COMMUNITIES = 18;
asi.PICK_COLLAB_COMMUNITIES=19;
asi.PICK_DOCUMENT_AND_FOLDER = 20;
asi.PICK_MESSAGE = 21;
asi.PICK_PROCESS = 27;

asi.PickerObject = function(dname,oid,ot,rid,prot){
  this.displayName = dname;
  this.objectId = oid;
  this.objectType = ot;
  this.roleId = rid;
  this.isProtected = prot;
}

asi.PickerParameters = function (id) {
  this.id = id ? id: 'asiPicker';
  this.showButtons = true;
  this.templateJSP = 'components/picker/main.bg';
  this.removeCallbackFunction = null;
  this.addCallbackFunction = null;
  this.renderRowFunction = "asi.renderRow";
  
  this.pickedArray = new Array();
  
  this.pickerTargetId = null;
  this.pickerTargetName = null;
  this.pickerTargetType = null;

  this.isSinglePicker = false;
  
  this.pickType = null;
  this.customRef = null;
}

asi.pick = function(pickType, targetId, targetName, parameters, targetType) {
  parameters = parameters ? parameters : new asi.PickerParameters();
  if (asi.isRedundantPicker && pickType == 13) parameters.id = 'redundantContainer';
  parameters.pickType=pickType;
  if (targetId) parameters.pickerTargetId = targetId;
  if (targetName) parameters.pickerTargetName = targetName;
  if (targetType) parameters.pickerTargetType = targetType;
  
  asi.parameters[parameters.id] = parameters;
  var htmlGet = CONTEXT_PREFIX+parameters.templateJSP;
  htmlGet += '?type='+pickType;
  htmlGet += '&showButtons='+parameters.showButtons; 
  htmlGet += '&pickerId='+parameters.id; 
  htmlGet += '&customRef='+parameters.customRef;

  var pickerObj = getObject(parameters.id);
  if (!pickerObj) {
      pickerObj = document.body.appendChild(asi.getPickerDOM(parameters.id));  
  }
  if (pickerObj) {
    if (!parameters.isSinglePicker){
      pickerObj.className = pickerObj.className.replace(/ ?asiSinglePicker/g, '');     
      pickerObj.style.width = '660px';
    }
    pickerObj.innerHTML = asi.WAIT_PICKER;
    pickerObj.style.display = 'block';
    if(parameters.id == 'asiPicker' || parameters.id == 'redundantContainer') {
      centerInViewport(pickerObj);
      if (parameters.id != 'redundantContainer') {
        if(window.pickerFilter) pickerFilter.remove();
        window.pickerFilter = new Filter(null, null, 1000);
        if(!window.event) pickerObj.style.zIndex =1001;
      } else if (!window.event) pickerObj.style.zIndex =1002;
    }
  }  
  backgroundPageNoHistory(htmlGet);
  return true;
}

asi.singlePick = function(pickType, targetId, targetName, parameters){
  parameters = parameters ? parameters : new asi.PickerParameters();
  if (asi.isRedundantPicker && pickType == 13) parameters.id = 'redundantContainer';
  parameters.pickType=pickType;
  var pickerObj = getObject(parameters.id);
  if (!pickerObj ) {
      pickerObj = document.body.appendChild(asi.getPickerDOM(parameters.id));      
  }
  if (pickerObj.className.indexOf('asiSinglePicker')<0) {
    pickerObj.className += ' asiSinglePicker';
    pickerObj.style.width = '470px';
  }
  parameters.isSinglePicker = true;
  asi.pick(pickType, targetId, targetName, parameters);  
}

asi.updatePickerTargets = function (pickerId) {
  var targetNames = '';
  var targetIds = '';
  var targetTypes = '';
  var parameters = asi.getParameters(pickerId);
  var pickedArray = parameters.pickedArray;
  for (var i = 0; i <pickedArray.length; i++) {
      targetIds += pickedArray[i].objectId;
      targetNames += pickedArray[i].displayName;
      targetTypes += pickedArray[i].objectType;
      if (i < pickedArray.length -1) {
          targetIds += ', ';
          targetTypes += ', ';
          targetNames += '; ';
      }
  }
  targetNames += '';
  if (parameters.pickerTargetId&&getObject(parameters.pickerTargetId)) {
    getObject(parameters.pickerTargetId).value = targetIds;
    try{getObject(parameters.pickerTargetId).onchange();}catch(e){};
  }
  if (parameters.pickerTargetName&&getObject(parameters.pickerTargetName)) {
    getObject(parameters.pickerTargetName).value = targetNames;
    try{getObject(parameters.pickerTargetName).onchange();}catch(e){}
  }
  if (parameters.pickerTargetType&&getObject(parameters.pickerTargetType)) {
    getObject(parameters.pickerTargetType).value = targetTypes;
    try{getObject(parameters.pickerTargetType).onchange();}catch(e){}
  }
  getObject(pickerId, 1).display = 'none';
  asi.clearCheckBoxes();
}
asi.addToPicked = function(pickerId, row) {
  var parameters = asi.getParameters(pickerId);
  var pickedArray = parameters.pickedArray;
  var pickedObj;
  var rowId = asi.getGridRowId(row);
  var isDuplicateId = function () {
    for (var i = 0; i < pickedArray.length; i++) {
      if(pickedArray[i].objectId == rowId.tokenAt(0) && pickedArray[i].objectType == rowId.tokenAt(2)) return true;
    }
    return false;
  }
  if (!isDuplicateId()){
    pickedObj = new asi.PickerObject(rowId.tokenAt(1), rowId.tokenAt(0) ,rowId.tokenAt(2) ,DEFAULT_ROLE,false);
    pickedArray.push(pickedObj);
    if (parameters.addCallbackFunction) {
      eval(parameters.addCallbackFunction+"(pickedArray[pickedArray.length-1])");
    }
    asi.appendGridRow(eval(parameters.renderRowFunction+"(pickedObj, parameters)"), getObject(pickerId + '_results').childNodes[0]);
  }
  asi.clearCheckBoxes();
  asi.refreshResultList(pickerId);
}
asi.addDirectlyToPicked = function(pickerId, id, name, type) {
  var parameters = asi.getParameters(pickerId);
  var pickedArray = parameters.pickedArray;
  var pickedObj = new asi.PickerObject(name, id, type, DEFAULT_ROLE, false);
  pickedArray.push(pickedObj);
  if (parameters.addCallbackFunction) {
    eval(parameters.addCallbackFunction+"(pickedArray[pickedArray.length-1])");
  }
  asi.refreshResultList(pickerId);
}
asi.removeFromPicked = function(pickerId, row){
  var parameters = asi.getParameters(pickerId);
  var pickedArray = parameters.pickedArray;
  var rowId = asi.getGridRowId(row);
  for (var i = 0; i < pickedArray.length; i++) {
    if (pickedArray[i].objectId == rowId.tokenAt(0) && pickedArray[i].objectType == rowId.tokenAt(1)) {
      if (parameters.removeCallbackFunction) {
        eval(parameters.removeCallbackFunction+"(pickedArray[i])"); 
      }
      pickedArray.splice(i, 1);
      parameters.pickedArray = pickedArray;
      asi.removeGridRow(row);
      break;
    }
  }
  asi.clearCheckBoxes();
  asi.refreshResultList(pickerId);
}

asi.clearCheckBoxes = function() {
  if(getObject(asi.selectedTabId)){
    var box = getCheckboxInElement(getObject(asi.selectedTabId));
    if (box) 
      uncheckAll(document.getElementsByName(box.name));
  }
}

asi.refreshResultList = function(pickerId,calledFromPageLoad) {
  var parameters = asi.getParameters(pickerId);
  if (calledFromPageLoad == true && parameters.pickedArray.length==0) {
    return;
  } else if (calledFromPageLoad == true) {
    for(var i=0;i<parameters.pickedArray.length;i++) {
      asi.appendGridRow(eval(parameters.renderRowFunction+"(parameters.pickedArray[i], parameters)"), getObject(parameters.id + '_results').childNodes[0]);
    } 
  }
  setObjContents(parameters.id+'_counter', parameters.pickedArray.length);
  alternateRowClass(getObject(parameters.id+"_results").getElementsByTagName('TABLE')[0]);
}

asi.attachClickToAdd = function (grid, pickerId) {
  var isSinglePick = getObject(pickerId).className.indexOf('asiSinglePicker') != -1;
  if (grid) {         
    var rows = grid.getElementsByTagName('TR');
    for (var j=0; j<rows.length; j++) {
      row = rows[j];
      if (asi.canBePicked(row) && row.id!="expRow") {
        row.onclick = function(event) {
          asi.selectRow(this, event, true);
          if(isSinglePick)asi.getParameters(pickerId).pickedArray.length = 0;
          asi.addToPicked(pickerId, this);
          if (grid.tagName=='TABLE') {
            alternateRowClass(grid);
          } else {
            alternateRowClass(grid.getElementsByTagName('TABLE')[0]);
          }
        }
        row.onmouseover = function(){
            this.className += ' hover';
        }
        row.onmouseout = function(){
          this.className = this.className.replace(/ *hover/g, '');
        }
      }
    }   
    if (isSinglePick) { //preselect parent
        try{autoClick(getElementsByClassName('asiGridTD0', grid)[0].parentNode)}catch(e){}
    }
  }
 }
 asi.canBePicked = function (row) {
   var reservedRowClasses = /asiGridNoResults|asiGridCustom|asiGridToolbar|asiGridControls/g;
   return !reservedRowClasses.test(row.className);
 }

/* == PICKER: RENDER GRID ==  */
asi.renderRow = function(item, parameters){
    var tr = document.createElement('TR');
      tr.onclick = function(event){
        asi.selectRow(this, event, true);
      }
      tr.onmouseover = function(){
        this.className += ' hover';
      }
      tr.onmouseout = function(){
        this.className = this.className.replace(' hover', '');
      }
      var td0 = document.createElement('TD');{
        td0.className = 'asiGridTD0';
        var checkbox = document.createElement('INPUT');{
          checkbox.type = 'checkbox';
          checkbox.name = 'asiPickedList';
          checkbox.id = 'asiPickedList';
          checkbox.value = ""+item.objectId + "/"+item.objectType + "/"+item.roleId;
          if (item.isProtected) checkbox.disabled = true;
          td0.appendChild(checkbox);
        }
        tr.appendChild(td0);
      }
      var td1 = document.createElement('TD');{
        td1.className = 'asiGridTD1';
        var img = document.createElement('IMG');{
          var objType = item.objectType;
          if (parameters.pickType==asi.PICK_ASSIGNEES_AC||parameters.pickType==asi.PICK_ASSIGNEES) {
            if (objType==0)
              objType=asi.TYPES.USER;
            else if (objType==1)
              objType=asi.TYPES.GROUP;
            else if (objType==2 || objType==TYPE_ACTOR)
              objType="Role";
            else if (objType==3)
              objType="fromVariable";
            else if (objType==TYPE_EXPRESSION)
              objType=TYPE_EXPRESSION;
          }
          if (objType==asi.TYPES.USER) {
            img.src = CONTEXT_PREFIX + 'components/toolbar/img/userInfo.gif';
            img.alt = 'Click to see User details';
          }
          else if (objType==asi.TYPES.GROUP) {
            img.src = CONTEXT_PREFIX + 'components/toolbar/img/groupInfo.gif';
            img.alt = 'Click to see Group details';
          }
          else if (objType=="Role") {
            img.src = CONTEXT_PREFIX + 'process/img/menu_role.gif';
          }
          else if (objType==TYPE_EXPRESSION)
          {
            img.src = CONTEXT_PREFIX + 'components/expeditor/img/openEditor.gif';
          img.alt = item.displayName;
          }else if(objType == "fromVariable"){
            img.src = CONTEXT_PREFIX + 'components/toolbar/img/gear.gif';
            img.alt = item.displayName;
          }
          img.alt = item.displayName;
           if(objType=="Role" || objType=="fromVariable" || objType==TYPE_EXPRESSION) td1.appendChild(img);
           else if (objType==asi.TYPES.USER || objType==asi.TYPES.GROUP) {
             var a = document.createElement('A');{
               a.title = 'User Info';
               a.href = '#';
             var id = item.objectId;
               a.onclick = function (event) {
                 if (objType == TYPE_USER) doUserDetail(id);
                 if (objType == TYPE_GROUP) doGroupDetail(id);
                 if(event) event.cancelBubble = true;
                 return false;
               }
             }
             a.appendChild(img);
             td1.appendChild(a);
           }
        }
        var spacer = document.createTextNode(" ");{
          td1.appendChild(spacer);
        }
        if(objType==TYPE_EXPRESSION){
         var a = document.createElement('A');
           a.title = item.displayName;
           a.appendChild(document.createTextNode(item.displayName.truncate(15, true)));
           td1.appendChild(a);
          }
        else td1.appendChild(document.createTextNode(item.displayName));
        tr.appendChild(td1);
      }
      var tdDel = document.createElement('TD');{
        tdDel.className = 'asiRemoveGridItem';
        var a = document.createElement('A');{
          a.title = 'Remove';
          a.href='#';
          a.onclick = function(event){
            row = getContainerByTagName(this, 'TR');
            row.onclick(event);
            asi.removeFromPicked(parameters.id, row);
          }
          var span = document.createElement('SPAN');{
            span.appendChild(document.createTextNode('Remove'));
            a.appendChild(span);
          }
          tdDel.appendChild(a);
        }
        tr.appendChild(tdDel);
      }
      return tr;
}

/* == PICKER: DYNAMIC CONTAINER ==  */
asi.getPickerDOM = function(id){
  if (!id)
    id = 'asiPicker';
  div = document.createElement('DIV');{
    div.className = 'asiPicker asiDialog';
    div.id = id;
    div.style.border = '1px solid #333';
  }
  return div;
}
/*********************************
 TABS
*********************************/
importStyleSheet('/public/components/tabs/css/asiTabs.css');


asi.selectedTabId = null;
asi.selectTab = function(tabObject, tabTargetId, tabNumber) {
  var tabGroup = tabObject.parentNode;
  while (tabGroup && tabGroup.tagName != 'BODY'  &&  tabGroup.getElementsByTagName('INPUT').length == 0 && 
    (tabGroup.getElementsByTagName('LI').length == 0 || tabGroup.getElementsByTagName('LI')[0].parentNode.className.indexOf('Tabs') == -1)) {
    tabGroup = tabGroup.parentNode;
  }
  if (tabGroup) {  
  asi.resetTabs(tabGroup);
    if (tabGroup.getElementsByTagName('INPUT').length > 0) {
        items = tabGroup.getElementsByTagName('INPUT');
        tabs = items;
    } else {
        items = tabGroup.getElementsByTagName('LI');
    }
   if (!tabNumber) {
     var count = 0;
       while (tabObject.tagName != 'INPUT' && tabObject.tagName != 'LI') {
         tabObject = tabObject.parentNode;
       }
      var iterator = tabObject;
      var tabs = new Array();
      for (var i = 0; i < items.length; i++) {
        if (items[i].parentNode.className.indexOf('Tabs') != -1) {
          tabs[tabs.length] = items[i];
        }
      }
      while (iterator.nextSibling &&  count <= tabs.length) {
        iterator = iterator.nextSibling;
        if(iterator.nodeType == 1 && (iterator.tagName == 'INPUT' || iterator.tagName == 'LI') && iterator.className.indexOf('collapse') == -1){
            count++;      
        }
      }
      tabNumber = tabs.length - count;
    }
    asi.selectedTabId = tabTargetId;
    for (var i = 0; i < tabs.length; i++) {
      var tab = tabs[i];
      if (i == tabNumber -1) {
        tab.className += ' current';
        break;
      }
    }
  }  
  if(getObject(tabTargetId)){
  asi.hideTabViews(tabTargetId);
    getObject(tabTargetId, 1).display = 'block';
  }
}
asi.hideTabViews = function(tabTargetId) {
  targets = getObject(tabTargetId).parentNode.childNodes;
  if (getObject(tabTargetId).parentNode.tagName == 'LI') { /* nav tabs case */
      targets = getElementsByClassName('asiTabTarget', getObject(tabTargetId).parentNode.parentNode);
  }
  for (var i = 0; i < targets.length; i++) {
    if(targets[i].nodeType == 1)targets[i].style.display = 'none';
  }
}
asi.resetTabs = function (tabGroup) {
  if (tabGroup.getElementsByTagName('INPUT').length > 0) {
      tabs = tabGroup.getElementsByTagName('INPUT');
  } else {
      tabs = tabGroup.getElementsByTagName('LI');
  }
  for (var i = 0; i < tabs.length; i++) {
    tabs[i].className = tabs[i].className.replace(/ *current/g, '');
  }
}
asi.switchTabAndGetContents = function (element,id,url) {
  asi.selectTab(element,id);
  var childNodes = getNodeElements(getObject(id).childNodes);
  if (childNodes.length==1 && childNodes[0].className=='asiWait') {
    backgroundPage(url);
    element.loaded = 'true';
  }
}
/*********************************
 COLLAPSABLE BLOCKS
*********************************/

asi.toggleCollapse = function(object, bubbleLevels){
  if (object.className == "") {
    object.className = ' collapsed';
    if (object.parentNode && bubbleLevels && bubbleLevels > 0) {
      asi.toggleCollapse(object.parentNode, bubbleLevels -1);
    }    
  } else {
    if (object.className.indexOf('collapsed') == -1) {
      asi.collapse(object, bubbleLevels);
    } else {
      asi.uncollapse(object, bubbleLevels);
    } 
  }
}
asi.collapse = function(object, bubbleLevels){
  object.className += ' collapsed';
  if (object.parentNode && bubbleLevels && bubbleLevels > 0) {
    asi.collapse(object.parentNode, bubbleLevels -1);
  }    
}
asi.uncollapse = function(object, bubbleLevels){
  object.className = object.className.replace(' collapsed', '');
  if (object.parentNode && bubbleLevels && bubbleLevels > 0) {
    asi.uncollapse(object.parentNode, bubbleLevels -1);
  }    
}
/*********************************
  DROPDOWN
*********************************/
importStyleSheet('/public/components/dropdown/css/asiDropdown.css');
/*********************************
 WIZARD COMPONENT
*********************************/
importStyleSheet('/public/components/wizard/css/asiWizard.css');

asi.WizardParameters = function (id) {
  this.id = id || 'asiWizard';
  this.currentStep = 1;
  this.WizMarkers = null;
  this.numSteps = 0;
  this.WizTarget = null;
  this.WizContents = null;
  this.WizForms = new Array();
  this.SubmissionForm = null;
}
asi.wizStepTo = function (wizObject, wizTarget, parameters) {
  if (!wizTarget) {
      var WizMarkers = getElementsByClassName('asiWizMarkers', wizObject)[0];
      asi.wizGenerateMarkers(WizMarkers);
      asi.wizGenerateTitles(wizObject.parentNode, WizMarkers);
      asi.wizAppendKeyListeners(wizObject);
      var marker = WizMarkers.getElementsByTagName('LI')[0];
      marker.className += ' next';
      marker.onclick();
      return;
  }
  if (typeof wizTarget == 'number') {
      var WizMarkers = 
        wizObject.className.indexOf('asiWizMarkers') == -1 ?
        getElementsByClassName('asiWizMarkers', wizObject)[0] :
        wizObject;
      var marker = WizMarkers.getElementsByTagName('LI')[wizTarget -1];
      marker.className += ' next';
      marker.onclick();
      return;
  }
  if (wizObject.className.search(/[next|done]/g) == -1) {
      return;
  }
  if (!parameters) {
    var parameters = new asi.WizardParameters();
    parameters.currentStep =  wizObject.stepNumber;
    parameters.WizMarkers = wizObject.parentNode;
    parameters.numSteps = getNodeElements(parameters.WizMarkers.childNodes).length;
    parameters.WizTarget = getObject(wizTarget);
    parameters.WizContents = getObject(wizTarget).parentNode;
    parameters.WizForms = parameters.WizMarkers.parentNode.getElementsByTagName('FORM');
    parameters.SubmissionForm = parameters.WizForms[parameters.WizForms.length -1];
    asi.parameters[parameters.id] = parameters;
  }
  if (parameters.currentStep != 1 && !standardValidate(parameters.WizForms[parameters.currentStep -2])) {
    parameters.currentStep --;
    return;
  }
  if(parameters.currentStep != 1) asi.wizTransferData(parameters);
  asi.wizUpdateMarkers(parameters.WizMarkers, parameters.currentStep);
  asi.hideTabViews(wizTarget);
  var currentForm = getObject(wizTarget);
  currentForm.style.display = 'block';  
  var elements = currentForm.elements;
  for (var i = 0; i < elements.length; i++) {
   if (isField(elements[i])) try{elements[i].focus(); return;}catch(e){continue}
  }
}
asi.wizNext = function (id) {
  var id = id || 'asiWizard';
  var parameters = asi.getParameters(id);
  if (parameters.currentStep >= parameters.numSteps) {
      asi.wizEnd(id);
      return;
  }
  parameters.currentStep ++;
  asi.wizStepTo(parameters.WizMarkers, parameters.currentStep);
}
asi.wizPrevious = function (id) {
  var id = id || 'asiWizard';
  var parameters = asi.getParameters(id);
  if (parameters.currentStep <= 1) {
      return;
  }
  parameters.currentStep --;
  asi.wizStepTo(parameters.WizMarkers, parameters.currentStep);
}
asi.wizConfirm = function (targetId, id) {
  var id = id || 'asiWizard';
  var parameters = asi.getParameters(id);
  var targetFields = getObject(targetId).elements;
  var wizForms = parameters.WizContents.getElementsByTagName('FORM');
  for (var i = 0; i < wizForms.length -1; i++) {
    var form = wizForms[i];
    if (form.id == targetId) continue;
    var fields = form.elements;
    for (var j = 0; j < fields.length; j++) {
      for (var k = 0; k < targetFields.length; k++) {
        if (isField(fields[j]) && fields[j].name == targetFields[k].name) {
            if(getFieldValue(fields[j], true)) targetFields[k].value = getFieldValue(fields[j], true);
            if(fields[j].type == 'checkbox' && getFieldValue(fields[j]) == '') targetFields[k].value = '';
            removeErrorContainer(fields[j]);
        }
      }
    }
  }
}
asi.wizEnd = function (id) {
  var id = id || 'asiWizard';
  var parameters = asi.getParameters(id);
  try {parameters.SubmissionForm.onsubmit();} catch(e){}
  parameters.SubmissionForm.submit();
}
asi.wizSubmitQuery = function (action, targetElement, id) {
  action = action.replace(/\.do/,'.bg');
  var targetObj =  getObject(targetElement);
  targetObj.innerHTML = asi.WAIT;
  targetObj.childNodes[0].style.height = '200px'
  var id = id || 'asiWizard';
  var parameters = asi.getParameters(id);
  asi.wizTransferData(parameters, 1);
  var form = parameters.SubmissionForm;
  var origAction = form.action;
  var origTarget = form.target;
  if (action.indexOf("/") == 0) action = action.substring(1, action.length);
  action += (action.indexOf("?") > -1) ? "&" : "?";
  form.action = CONTEXT_PREFIX + action + "$e=" + targetElement;
  form.target = 'fProcess';
  form.submit();
  form.action = origAction;
  form.target = origTarget;
}
asi.wizServerValidate = function (action, id) {
  var id = id || 'asiWizard';
  var parameters = asi.getParameters(id);
  var form = null;
  var isSingleStep = false;
  if (parameters.WizForms.length > 0) {
    if (!standardValidate(parameters.WizForms[parameters.currentStep -1])) return;
    asi.wizTransferData(parameters, 1);
    form = parameters.SubmissionForm;      
  } else {
    form = document.forms[document.forms.length -1];
    if (!standardValidate(form)) return;
    parameters.currentStep = 1;
    isSingleStep = true;
  }
  var origAction = form.action;
  var origTarget = form.target;
  action = action.replace(/\.do/,'.none');
  if (action.indexOf("/") == 0) action = action.substring(1, action.length);
  action += (action.indexOf("?") > -1) ? "&" : "?";
  action = CONTEXT_PREFIX + action + "$replace=false&$form_state=validate&$ws=" + parameters.currentStep + "&$wid=" + parameters.id + '&isSingleStep=' + isSingleStep;
  form.action = action;
  form.target = 'fProcess';
  form.submit();
  form.action = origAction;
  form.target = origTarget;
}
asi.wizSetErrorMessage = function (property, message, id) {
  var id = id || 'asiWizard';
  var parameters = asi.getParameters(id);
  var step = parameters.currentStep;
  var isSingleStep = typeof parameters.WizForms == 'undefined';
  for (var i = 0; i < step; i++) {
    var stepElements = isSingleStep? document.forms[document.forms.length -1].elements : parameters.WizForms[i].elements;
    for (var j = 0; j < stepElements.length; j++) {
      var field = stepElements[j];
      if (isField(field) && field.name == property) {
        setFieldError(field, message);
      }
    }
  }
}
asi.wizNormalizeConfirmForm = function (id) {
  var id = id || 'asiWizard';
  var parameters = asi.getParameters(id);
  asi.wizNormalizeForm(parameters.WizForms[parameters.WizForms.length -2]);
}
asi.wizNormalizeSubmitForm = function (id) {
  var id = id || 'asiWizard';
  var parameters = asi.getParameters(id);
  asi.wizNormalizeForm(parameters.SubmissionForm, true);
}
/* = wiz private functions = */
asi.wizNormalizeForm = function (form, toHidden) {
  var element, normalizedField;
  var fieldsets = form.getElementsByTagName('FIELDSET');
  for (var i = 0; i < fieldsets.length; i++) {fieldsets[i].className = 'readonly'}
  if (toHidden) {for (var i = 0; i < fieldsets.length; i++) {extractFields(fieldsets[i], true);}}
  var buttons = getInputsByType('button', form);
  for (var i = 0; i < buttons.length; i++) {
    if (buttons[i].className.indexOf('iBlueButton') == -1) buttons[i].parentNode.removeChild(buttons[i]);
  }
  var booleans = getElementsByAttributeValue('class', 'booleanInput', form, 'INPUT');
  for (var i = 0; i < booleans.length; i++) {
    if (!booleans[i].checked){      
      booleans[i].parentNode.parentNode.removeChild(booleans[i].parentNode);
    }
  }

  for (var i = 0; i < form.elements.length; i++) {
    element = form.elements[i];
    i += asi.wizNormalizeField(element, toHidden);
  }
}
asi.wizNormalizeField = function (field, toHidden) {
  if (field.type == 'hidden') toHidden = true;
  removeErrorContainer(field);
  if ((field.type == 'button' || field.type == 'submit') && field.className.indexOf('iBlueButton') == -1){
    field.parentNode.removeChild(field);
    return -1;
  }
  if (!toHidden && field.tagName == 'TEXTAREA') return 0;
  if (isField(field) && (field.className != 'booleanInput' || field.className == 'booleanInput' && field.checked)) {
    var normalizedField;
    var label = getFieldLabel(field);
    var isGroupTypeAttribute = field.name.match(/value\d/);
    if(label && !toHidden) label = label.replace(/\*/g,'');
    if(field.className == 'booleanInput') label = label.replace(/( ?Yes)|( ?No)/i, '');
    var value = getFieldValue(field);
    var innerString = "";
    if(!toHidden) innerString += "<label class='readOnly'><span ";
    if (isGroupTypeAttribute) innerString += 'style="display: none" ';
    innerString += "class='label'>" + label + "</span>";
    innerString += " <input value='" + value + "' name='" + field.name + "'";
    innerString += ' readonly="true"';
    if(toHidden) innerString += ' type="hidden"'; 
    innerString += ">";
    if(!toHidden) innerString += "</label>";
    var spanField = field.parentNode.parentNode;
    normalizedField = document.createElement('SPAN');
    normalizedField.style.display = 'block';
    normalizedField.innerHTML = innerString;
    if(spanField.tagName == 'SPAN' && spanField.style.display != 'block'){
      spanField.replaceChild(normalizedField, field.parentNode);
      return 0;
    } else if (spanField.className == 'fields') {
        var input = document.createElement('INPUT');
          input.name = field.name;
          input.className = 'iText';
          input.style.border = 0;
          input.value = value;
          input.readOnly = true;
          if (toHidden) input.type = 'hidden';
        var fieldLinks = field.parentNode.getElementsByTagName('A');
        for (var i = 0; i < fieldLinks.length; i++) {
          fieldLinks[i].parentNode.removeChild(fieldLinks[i]);
        }
        if (field.className == 'booleanInput') {
          var label = getElementsByClassName('label', field.parentNode, 'SPAN')[0];
          if (label) field.parentNode.removeChild(label);
        }
        field.parentNode.replaceChild(input, field);
        return 0;
    }
  } else if (field.className == 'booleanInput' && !field.checked){
    field.parentNode.parentNode.removeChild(field.parentNode);
    return -1;
  }
  return 0;
}
asi.wizNormalizeFieldsets = function (fieldsets) {
  for (var i = 0; i < fieldsets.length; i++) {
    var invalid = fieldsets[i].getElementsByTagName('FIELDSET');
      for (var j = 0; j < invalid.length; j++) {
        extractFields(invalid[j]);
      }
  }
}
asi.wizUpdateMarkers = function (wizMarkers, stepNum) {
  var markers = getNodeElements(wizMarkers.childNodes);
  for (var i = 0; i < markers.length; i++) {
    if (markers[i].nodeType != 1) continue;
    markers[i].className = markers[i].className.replace(/[done|current|next]/g, '');
    if (i < stepNum -1) {
        markers[i].className += ' done';
    } else if (i == stepNum -1) {
        markers[i].className += ' current';
    } else if (i == stepNum) {
        markers[i].className += ' next';
    }
  }
}
asi.wizTransferData = function (parameters, stepShift){
  asi.wizClearFields(parameters);
  var wizForms = parameters.WizForms;
  var wizFields = parameters.SubmissionForm.elements;
  var shift = stepShift ? (stepShift -2) : -2;
  var fields = wizForms[parameters.currentStep + shift].elements;
  for (var i = 0; i < fields.length; i++) {
    if (fields[i].type != 'button'  && fields[i].tagName != 'FIELDSET') {
      for (var j = 0; j < wizFields.length; j++) {
        if (fields[i].name == wizFields[j].name) {
          if (getFieldValue(fields[i])) wizFields[j].value = getFieldValue(fields[i]);
          for (var k = 0; k < wizFields.length; k++) {
            if (fields[i].name + '_label' == wizFields[k].name && getFieldValue(fields[i])) wizFields[k].value = getFieldValue(fields[i], true);
          }
          break;
        }
      } 
    }
  }
}
asi.wizClearFields = function (parameters) {
  if (parameters.currentStep == parameters.WizForms.length -1) return;
  if (parameters.WizForms[parameters.currentStep -1].id.toLowerCase().indexOf('confirm') > -1) return;
  var fields = parameters.WizForms[parameters.currentStep -1].elements;
  var wizFields = parameters.SubmissionForm.elements;
  for (var i = 0; i < fields.length; i++) {
    if (fields[i].type == 'checkbox') {
      for (var j = 0; j < wizFields.length; j++) {
        if (fields[i].name == wizFields[j].name) {
          wizFields[j].value = '';
          for (var k = 0; k < wizFields.length; k++) {
            if (fields[i].name + '_label' == wizFields[k].name) wizFields[k].value = '';
          }
          break;
        }
      } 
    }
  }
}
asi.wizGenerateMarkers = function (wizMarkers) {
  var markers = wizMarkers.childNodes;
  var shift = 0;
  for (var i = 0; i < markers.length; i++) {
    if (markers[i].nodeType == 1 && markers[i].tagName == 'LI') {
      markers[i].stepNumber = i + 1 - shift;
    } else {
        shift++;
    }
  }
}
asi.wizGenerateTitles = function (wizContainer, wizMarkers) {
  if (wizContainer.className.match(/noMarkers/)) return;
  if (!executeJavaScript()) return;
  var wizForms = wizContainer.getElementsByTagName('FORM');
  for (var i = 0; i < wizForms.length; i++) {
    var form = wizForms[i];
    var headerInfo = getElementsByClassName('headerInfo', form, 'LI')[0];
    var marker = wizMarkers.getElementsByTagName('LI')[i];
    if (!marker) continue;
    var title = 'Step ' + marker.stepNumber + ': ';
    if (headerInfo) {
      title += headerInfo.getElementsByTagName('LEGEND')[0].innerHTML;        
    } else {
      var ul = form.getElementsByTagName('UL')[0];
      headerInfo = ul.insertBefore(document.createElement('LI'), ul.childNodes[0]);
      title += marker.innerHTML;
    }
    var instructions = getElementsByClassName('formInstructions', form, 'P')[0];
    headerInfo.className = 'wizardStepHeader';
    while (headerInfo.hasChildNodes()) headerInfo.removeChild(headerInfo.childNodes[0]);
    var stepHeaderTitle = headerInfo.appendChild(document.createElement('H2'));
    stepHeaderTitle.innerHTML = title;
    if (instructions) {
      var stepInstructions = headerInfo.appendChild(document.createElement('SPAN'));
      stepInstructions.innerHTML = instructions.innerHTML;
      var instructionsContainer = getContainerByTagName(instructions, 'LI');
      instructionsContainer.parentNode.removeChild(instructionsContainer);
    }
  }
}
asi.wizAppendKeyListeners = function (wizObject) {
  var wizForms = wizObject.getElementsByTagName('FORM');
  for (var i = 0; i < wizForms.length; i++) {
    wizForms[i].onkeypress = function (evt) {
      formListener(evt);
    }
  }
}
/*********************************
 GRID COMPONENT
*********************************/
importStyleSheet('/public/components/grid/css/asiGrid.css');


asi.Grid = function (id) {
  this.id = id;
  this.rowIds = new Array();
  this.rows = new Array();
  this.getRowById = function (id, index){
    return this.rows[this.getRowIndex(id, index)];
  }
  this.addRow = function (TR) {
    var rowId = asi.getGridRowId(TR);
    if (!this.isDuplicateId(rowId)) {
      this.rowIds.push(rowId);
      this.rows.push(TR);
    }
  }
  this.removeRowById = function (id, index){
    this.removeRow(this.getRowIndex(id, index));
  }
  this.removeRow = function (index) {
    this.rowIds.splice(index, 1);
    this.rows.splice(index, 1);
  }
  this.getRowIndex = function (id, index){
    for (var i = 0; i < this.rowIds.length; i++) {
      if(this.rowIds[i].tokenAt(index) == id) return i;
    }
    return -1;
  }
  this.isDuplicateId = function (id, index) {
    for (var i = 0; i < this.rowIds.length; i++) {
      if(this.rowIds[i].tokenAt(index) == id) return true;
    }
    return false;
  }
  this.containerDOM = asi.getGridDOM(this.id);
  this.DOM = function () {
    gridTable = this.containerDOM.getElementsByTagName('TABLE')[0];
    for (var i = 0; i < this.rows.length; i++) {
      asi.appendGridRow(this.rows[i], gridTable);    
    }
    return gridTable;
  }
}
asi.getGridDOM = function (id) {
    var table = document.createElement('TABLE');{
      table.id = "asi_grid_" + id;
      table.className = 'asiGrid';
      table.cellspacing = 0;
      var tbody = document.createElement('TBODY');{
        table.appendChild(tbody);
      }
    }
  return table;
}
asi.getGridTR = function (rowObject) {
  if (typeof rowObject == 'object' && rowObject.length){
    var tr = document.createElement('TR');
    for (var i = 0; i < rowObject.length; i++) {
      var td = document.createElement('TD');{
        td.className = 'asiGridTD' + i;
        if (rowObject[i].tagName){
          td.appendChild(rowObject[i]);
        } else {
          td.innerHTML = rowObject[i];            
        }
        tr.appendChild(td);
      }
    }
    return tr;
  }
  if (rowObject.self) return rowObject.self;
}
asi.appendGridRow = function (TR, targetTable) {
  if (TR.tagName != 'TR') return false;
  if (targetTable.tagName != 'TABLE') return false;
  if (getElementsByClassName('asiGridNoResults', targetTable).length > 0) removeElement(getElementsByClassName('asiGridNoResults', targetTable)[0]);
  targetTable.getElementsByTagName('TBODY')[0].appendChild(TR);
}
asi.removeGridRow = function(object){
  var row = getContainerByTagName(object, 'TR');
  row.parentNode.removeChild(row);
}
asi.isDuplicateRow = function (id, table, index) {
  var ids = asi.getGridIds(table);
  for (var i = 0; i < ids.length; i++) {
    if (index || index == 0) {
        var tokens = ids[i].split("/");
        if(tokens[index] == id) return true;
    }
    if(ids[i] == id) return true;
  }
  return false;
}
asi.getGridRowId = function (TR, index) {
  var checkboxes = getInputsByType('checkbox', TR);
  var chkValue = checkboxes.length > 0 ? checkboxes[0].value : null;
  if (index || index == 0) {
      var tokens = chkValue.split("/");
      return tokens[index];
  }
  return chkValue;
}
asi.getGridRowIcon = function (TR) {
  return TR.getElementsByTagName('IMG')[0];
}
asi.modifyRowValue = function (object, value, index) {
  var values = getInputsByType('checkbox', getContainerByTagName(object, 'TR'))[0].value.split("/");
  values[index] = value;
  getInputsByType('checkbox', getContainerByTagName(object, 'TR'))[0].value = values.join("/");
}
asi.getGridIds = function (gridMain) {
  var gridData = new Array();
  var gridRows = gridMain.getElementsByTagName('TR');
  for (var i = 0; i < gridRows.length; i++) {
    if (asi.getGridRowId(gridRows[i])) gridData[gridData.length] = asi.getGridRowId(gridRows[i]);
  }
  return gridData;
}
/* = grid object generators =  */
asi.getCheckboxForGrid = function () {
  return "<input type=\"checkbox\" value=\"" + joinArgs(arguments) + "\"/>";
}
asi.getUserForGrid = function (id, displayName, type, hasIcon) {
  var out = "";
  if (hasIcon && (type == asi.TYPES.USER || type == asi.TYPES.GROUP)) {
      out += "<img src=\"" + CONTEXT_PREFIX + "personalization/img/";
      out += (type == asi.TYPES.USER) ? "menu_user.gif" : "menu_group.gif";
      out += "\" alt=\"Icon\">";
  }
  out += "<a href=\"#\" onclick=\"";
  out += (type == asi.TYPES.USER) ? "doUserDetail('" : "doGroupDetail('" ;
  out += id;
  out += "');\">";
  out += displayName;
  out += "</a>"
  return out;
}
asi.getRadioForGrid = function (property, value, isChecked) {
  var out = "<input type=\"radio\" name=\"" + property + "\" value=\"" + value + "\"";
  if (isChecked) out += " checked=\"true\"";
  if (arguments[3]) out+= arguments[3];
  out += " />";
  return out;
}
asi.getDeleteForGrid = function (isProtected) {
  return isProtected ? "&nbsp;" : "<a href=\"#\" onclick=\"asi.removeGridRow(this); return false;\" ><img class=\"imgOnly\" src=\"" + CONTEXT_PREFIX + "components/img/delete.gif\" alt=\"Remove\" align=\"absmiddle\"></a>";
}
/* = grid views = */
asi.GRID_DEFAULT_VIEW = 0;
asi.GRID_DETAIL_VIEW = 1;
asi.GRID_LIST_VIEW = 2;
asi.changeGridViewType = function(object, viewType){
  var container = object;
  while (container && container.tagName != 'BODY'  &&  container.getElementsByTagName('TABLE').length == 0) {
    container = container.parentNode;
  }
  if (container) {
    switch (viewType) {
        case asi.GRID_DEFAULT_VIEW :
            container.className = '';
            break;
        case asi.GRID_DETAIL_VIEW :
            container.className = 'asiGridDetailView';
            break;
        case asi.GRID_LIST_VIEW :
            container.className = 'asiGridListView';
            break;
        default :
            container.className = '';
    }
  }
}
/*********************************
 TABLE FUNCTIONS
*********************************/
asi.stripeTable = function(id) {
  var even = false;
  var evenColor = arguments[1] ? arguments[1] : "transparent";
  var oddColor = arguments[2] ? arguments[2] : "#daf0f9";
  var table = getObject(id);
  if (!table) { return; }
  var tbodies = table.getElementsByTagName("tbody");
  for (var h = 0; h < tbodies.length; h++) {
    var trs = tbodies[h].getElementsByTagName("tr");
    for (var i = 0; i < trs.length; i++) {
        trs[i].style.backgroundColor = (i%2==0) ? evenColor : oddColor;
    }
  }
}
asi.selectRow = function(object, evt, isExclusive) {
  evt = (evt) ? evt : (event) ? event : '';
  if (evt) {
    target = (evt.target) ? evt.target : event.srcElement;
    if (target.tagName == 'A' && target.parentNode.className != 'asiRemoveGridItem') {return;} //disable on anchors (for item info)
  } else {
      target = null;
  }
  var checkbox = getCheckboxInElement(object);
  isChecked = checkbox.checked;
  checkboxContainer = 
    checkbox.form ? checkbox.form : getContainerByTagName(checkbox, 'TABLE');
  if (isExclusive) asi.clearRows(checkboxContainer);
  if (target && target.tagName == 'INPUT') {
  isChecked = !isChecked;
  }
  if (isChecked) {
      object.className = object.className.replace(' selected', '');
  } else {
      object.className += ' selected';
  }
  checkbox.checked = !isChecked;
  }

  asi.clearRows = function(container) {
    opts = container.getElementsByTagName('LI');
    if(opts.length == 0 || container.tagName == 'TABLE') opts = container.getElementsByTagName('TR');
    for (var i = 0; i < opts.length; i++) {
      opts[i].className = opts[i].className.replace(/(^|[ ]*)selected/g, '');
      fields = opts[i].getElementsByTagName('INPUT');
      for (j = 0; j < fields.length; j++) {
        fields[j].checked = false;
      }
    }
  }
asi.markEven = function(id) {
  var even = false;
  var table = getObject(id);
  if (!table) { return; }
  var tbodies = table.getElementsByTagName("tbody");
  for (var h = 0; h < tbodies.length; h++) {
    var trs = tbodies[h].getElementsByTagName("tr");
    for (var i = 0; i < trs.length; i++) {
        var tr = trs[i];
        if(tr.className.indexOf('even') == -1 && (i%2 == 0)) tr.className += ' even';
    }
  }
}

/*********************************
  ALERT
*********************************/

asi.prompt = function(title, onOK, type,  label, property, validate, required, instructions, value, maxlength){
  if (arguments[2] instanceof Array) getPrompt.apply(this, arguments);
  else getPrompt(title, onOK, sliceArgs(arguments, 2));
  function getPrompt() {
    var numEntries = arguments.length - 2;
    var URL = '/components/dialogs/prompt.do?$e=asiDialog&numEntries=' + numEntries;
      URL += '&title=' + arguments[0];
      URL += '&onOK=' + arguments[1];
    for (var i = 0; i < numEntries; i++) {
      var entry = arguments[i + 2];
      if (entry.length < 3) throw new Error('Missing arguments: required arguments are type, title, label, property and onOK');
      URL += '&type=' + entry[0];
      URL += '&label=' + entry[1];
      URL += '&property=' + entry[2];
      if (entry[3]) URL += '&validate=' + entry[3];
      URL += '&required=' + entry[4] || false;
      if (entry[5]) URL += '&instructions=' + entry[5];
      if (entry[6]) URL += '&value=' + entry[6];
      if (entry[7]) URL += '&maxlength=' + entry[7];
    }
    backgroundPage(URL);      
  }
}

asi.alert = function(text){
asi.initializeAlert.apply(this, arguments);
  getObject('asiAlertCancel', 1).display = 'none';
  getObject('asiAlertOK').focus();
}

asi.confirm = function(text){
asi.initializeAlert.apply(this, arguments);
  getObject('asiAlertCancel', 1).display = 'inline';
  getObject('asiAlertOK').focus();
  if (text.match(/\?$/)) {
    getObject('asiAlertOK').value = "Yes"
    getObject('asiAlertCancel').value = "No";
  }
}
/* = alert/confirm private functions = */
asi.initializeAlert = function (text) {
  var alertBox = getObject('asiAlert')
  if (alertBox) alertBox.parentNode.removeChild(alertBox);
  document.body.appendChild(asi.getAlertDOM(arguments));
  if(window.alertFilter) alertFilter.remove();
  window.alertFilter = new Filter(null, null, 5000);
  centerInViewport(getObject('asiAlert'));
  getObject('asiAlert').style.display = 'block';
  getObject('asiAlertText').innerHTML = text;
}
asi.getAlertDOM = function(alertArgs){
  div = document.createElement('DIV');{
    div.className='asiDialog';
    div.id = 'asiAlert';
    h1 = document.createElement('H1');{
      h1.appendChild(document.createTextNode('Appian Enterprise'));
      div.appendChild(h1);
    }
    p = document.createElement('P');{
      p.id = 'asiAlertText';
      div.appendChild(p);
    }
    p = document.createElement('P');{
      input = document.createElement('INPUT');{
        input.type = 'button';
        input.value= 'OK';
        input.className = 'iBlueButton';
        input.id = 'asiAlertOK';
        input.onclick = function(){
          var thisDOM = getObject('asiAlert');   
          thisDOM.parentNode.removeChild(thisDOM);
          window.alertFilter.remove();
          if (!alertArgs) return true;
          if (!alertArgs.length || alertArgs.length < 2) return true;
          if (alertArgs[1] && typeof alertArgs[1] == 'function') {
            if (alertArgs[2] && typeof alertArgs[2] == 'object' && alertArgs[2].length) {
                alertArgs[1].apply(this, alertArgs[2]);
            } else {
                alertArgs[1].call(this);
            }
          } else if (alertArgs[1] && typeof alertArgs[1] == 'string') {
              eval(alertArgs[1]);
          }
        }
        p.appendChild(input);
      }
      input = document.createElement('INPUT');{
        input.type = 'button';
        input.value = 'Cancel';
        input.className = "iBlueButton";
        input.id = 'asiAlertCancel';
        input.onclick = function(){
          var thisDOM = getObject('asiAlert');   
          thisDOM.parentNode.removeChild(thisDOM);  
          window.alertFilter.remove();
          if (!alertArgs.length || alertArgs.length < 2) return false;
          if (alertArgs[2] && typeof alertArgs[2] == 'function') {
            if (alertArgs[3] && typeof alertArgs[3] == 'object' && alertArgs[3].length) {
                alertArgs[2].apply(this, alertArgs[3]);
            } else {
                alertArgs[2].call(this);
            }
          } else if (alertArgs[3] && typeof alertArgs[3] == 'function') {
            if (alertArgs[4] && typeof alertArgs[4] == 'object' && alertArgs[4].length) {
                alertArgs[3].apply(this, alertArgs[4]);
            } else {
                alertArgs[3].call(this);
            }              
          } else if (alertArgs[2] && typeof alertArgs[2] == 'string') {
              eval(alertArgs[2]);
          } else if (alertArgs[3] && typeof alertArgs[3] == 'string') {
              eval(alertArgs[3]);
          }
        }
        p.appendChild(input);
      }
      div.appendChild(p);
    }
  }
  return div;
}
/*********************************
 EXPRESSION EDITOR COMPONENT
*********************************/
importStyleSheet("/public/components/expeditor/css/asiExpressionEditor.css");
var AEE;

/**
 * Expression Editor constructor.  Component will also be stored in JS memory as AEE.  Target field is a reference to the field
 * where you want the expression to be inserted.  pvNameArray is an array of process variables that can be used in the expression.
 * acpNameArray is similar.  isTask indicates that the Expression Editor is being used to configure a node, therefore task properties
 * should be available.  isPartial is used to accomodate situations where an assignment expression is constructed from several
 * different fields; setting it to true causes the editor to automatically strip any opening '=' symbols, which is opposite of
 * the Editor's usual behavior.  isPartial is false by default.
 * @since 4.0
 */
function ExpressionEditor(targetField, pvNameArray, acpNameArray, isTask, isPartial, isTargetHidden,acpGeneratedArray){
  AEE = this;
  this.targetField = targetField;
  this.expTextArea = null;
  this.pvNameArray = pvNameArray;
  this.acpNameArray = acpNameArray;
  this.currentFunction = null;
  this.isPartial = isPartial || false;
  this.isTargetHidden = isTargetHidden || false;
  this.acpGeneratedArray = acpGeneratedArray;

  this.type = "ExpressionEditor";
  this.component = Component;
  this.component(EXPRESSIONEDITOR.EDITORID, this.stylesheets, this.type);

  this.repopulateVariablePalette = function(){
    window.varPalette = new Hierarchy('varPalette');
    varPalette.DOM.innerHTML = "";
    
    var paletteHTMLArray = new Array;
    paletteHTMLArray[0] = '<a onclick="toggleNode(this)" title="Process Variables">Process Variables</a>';
    if(this.pvNameArray && this.pvNameArray[0] && this.pvNameArray[0] != "")
      for(var i=0; i<this.pvNameArray.length; i++) {
        paletteHTMLArray[i+1] = EXPRESSIONEDITOR.createPaletteNodeHTML(this.pvNameArray[i],'pv');
      }
    else
      paletteHTMLArray[1] = 'This Process has no Variables';
    varPalette.populate(paletteHTMLArray);
    HIERARCHY.toggleNodeDrill('varPalette',0);

    if(this.acpNameArray && this.acpNameArray[0] != "") {
      paletteHTMLArray = new Array;
      paletteHTMLArray[0] = '<a onclick="toggleNode(this)" title="Activity Class Parameters">Activity Class Parameters</a>';
      for(var i=0; i<this.acpNameArray.length; i++) {
        if(this.acpGeneratedArray && this.acpGeneratedArray[i] && this.acpNameArray[i].indexOf('>')!=-1){
          i = EXPRESSIONEDITOR.createComplexACPHtml(this.acpNameArray,i,paletteHTMLArray);
        }else{
          paletteHTMLArray[i+1] = EXPRESSIONEDITOR.createPaletteNodeHTML(this.acpNameArray[i],'ac');
        }
      }
      varPalette.populate(paletteHTMLArray);
    }
    if(isTask){
      paletteHTMLArray = new Array;
      paletteHTMLArray[0] = '<a onclick="toggleNode(this)" title="Task Properties" onclick="varPalette.toggleNode(this)">Task Properties</a>';
      paletteHTMLArray[1] = EXPRESSIONEDITOR.createPaletteNodeHTML('id','tp');
      paletteHTMLArray[2] = EXPRESSIONEDITOR.createPaletteNodeHTML('name','tp');
      paletteHTMLArray[3] = EXPRESSIONEDITOR.createPaletteNodeHTML('description','tp');
      paletteHTMLArray[4] = EXPRESSIONEDITOR.createPaletteNodeHTML('display','tp');
      paletteHTMLArray[5] = EXPRESSIONEDITOR.createPaletteNodeHTML('priority','tp');
      paletteHTMLArray[6] = EXPRESSIONEDITOR.createPaletteNodeHTML('owner','tp');
      paletteHTMLArray[7] = EXPRESSIONEDITOR.createPaletteNodeHTML('assignees','tp');
      varPalette.populate(paletteHTMLArray);
    }
    paletteHTMLArray = new Array;
    paletteHTMLArray[0] = '<a onclick="toggleNode(this)" title="Process Properties">Process Properties</a>';
    paletteHTMLArray[1] = EXPRESSIONEDITOR.createPaletteNodeHTML('id','pp');
    paletteHTMLArray[2] = EXPRESSIONEDITOR.createPaletteNodeHTML('name','pp');
    paletteHTMLArray[3] = EXPRESSIONEDITOR.createPaletteNodeHTML('priority','pp');
    paletteHTMLArray[4] = EXPRESSIONEDITOR.createPaletteNodeHTML('initiator','pp');
    paletteHTMLArray[5] = EXPRESSIONEDITOR.createPaletteNodeHTML('designer','pp');
    varPalette.populate(paletteHTMLArray);

    paletteHTMLArray = new Array;
    paletteHTMLArray[0] = '<a onclick="toggleNode(this)" title="Process Model Properties">Process Model Properties</a>';
    paletteHTMLArray[1] = EXPRESSIONEDITOR.createPaletteNodeHTML('id','pm');
    paletteHTMLArray[2] = EXPRESSIONEDITOR.createPaletteNodeHTML('name','pm');
    paletteHTMLArray[3] = EXPRESSIONEDITOR.createPaletteNodeHTML('description','pm');
    paletteHTMLArray[4] = EXPRESSIONEDITOR.createPaletteNodeHTML('version','pm');
    varPalette.populate(paletteHTMLArray, varPalette.DOM);
  }
/**
 * Inserts the value at the last location of the cursor within the expression text area.
 * @since 4.0
 */
  this.insertIntoExpression = function(value, isRegularFunction){
    insertAtCursor(this.range, value);
    if(isRegularFunction) moveCursor(this.expTextArea, -1);
    this.updateRange();
  }
/*
 * @since 4.1
 */
  this.updateRange = function(){
    var sel = document.selection;
    this.range = sel.createRange();
    if(this.range.parentElement() != this.expTextArea){
      this.expTextArea.focus();
      this.updateRange();
    }
  }
/**
 * Shows the information area for the function with the given name.  Hides the previously displayed information.
 * @since 4.0
 */
  this.showFunc = function(funcName){
    if(this.currentFunction) hideObject('function_' + this.currentFunction);
    showObject('function_' + funcName);
    this.currentFunction = funcName;
  }
/**
 * Don't use this.  It's for internal purposes only.  Call this.openEditor or
 * the Editor constructor instead.  If the Expression Editor has already been
 * opened on the page, this resets the JS object and repopulates the palette
 * variable.
 * @since 4.0
 */
  this.refreshEditor = function(){
    this.expTextArea = getObject("expTextArea");
    var defaultValue = this.targetField.value;
    if(!this.isPartial && defaultValue != '' && defaultValue.charAt(0) != '=') defaultValue = '"' + defaultValue.replace(/"/g,'""') + '"';
    this.expTextArea.value = defaultValue;
    this.repopulateVariablePalette();
    this.showFunc('welcome');
    isTask ? showObjectBlock('parseWarning') : hideObject('parseWarning');
    showObject(this.id);
    this.expTextArea.focus();
    this.updateRange();
    return getObject(this.id);
  }
/**
 * Opens the Expression Editor.
 * @since 4.0
 */
  this.openEditor = function () {
    if(getObject(this.id)) return this.refreshEditor();
    var editorContainer = EXPRESSIONEDITOR.getEditorDOM(this.id);
    centerInViewport(editorContainer);
    backgroundPage('process/viewFunctionCategories.bg?$e=' + this.id);
    return editorContainer;
  }
  this.DOM = this.openEditor();
/**
 * Performs validation on the Expression.  Returns false if there is an error or true if nothing is wrong.
 * @since 4.0
 */
  this.validateExp = function(){
    return EXPRESSIONEDITOR.lexer(this.expTextArea.value);
  }
/**
 * Closes the Editor and hides the current function helper text.
 * @since 4.0
 */
  this.closeEditor = function(){
    hideObject(this.id);
    if(this.currentFunction) hideObject('function_' + this.currentFunction);
  }
/**
 * Performs validation and puts the expression into the target field of the Editor.  Appends an = sign if isPartial is false or not specified.
 * Strips it if isPartial is true.
 * @since 4.0
 */
  this.saveAndClose = function(){
    if(! this.validateExp()) return null;
    theExpression = this.expTextArea.value;
    if(!this.isPartial && theExpression && theExpression.charAt(0) != '=')
      theExpression = '='.concat(theExpression);
    else if(this.isPartial && theExpression && theExpression.charAt(0) == '=')
      theExpression = theExpression.substring(1,theExpression.length);
    this.targetField.value = theExpression;
       try{this.targetField.focus()} catch(e){};
    if (this.targetField != null && this.targetField.onchange != null && typeof this.targetField.onchange != 'undefined')
      this.targetField.onchange();
    this.closeEditor();
  }
}

EXPRESSIONEDITOR = {
  EDITORID: "asiExpressionEditor",
 /**
  * Appends an operator button to the toolbar.  'symbol' is the text the button will insert into the Expression.  'imageName' is the
  * title of the gif for the button; the image must be in \comoponents\expeditor\img.
  * 'isNewButtonSection' is an optional boolean *specifying whether the button starts a new section of buttons.  False by default.
  * Use 'alternateOnClick' to pass in a non-standard onclick script for the button.
  * @since 4.0
  */
  addOperatorButton: function(symbol, imageName, alt, isNewButtonSection, alternateOnClick){
    var toolbar = getObject("operatorToolbar");
    var onclick = "AEE.insertIntoExpression('" + symbol + "')";
    if(alternateOnClick) onclick = alternateOnClick;
    var buttonDOM = EXPRESSIONEDITOR.getToolbarButtonDOM(onclick, imageName, alt);
    if(isNewButtonSection) buttonDOM.style.marginLeft = "10px";
    toolbar.appendChild(buttonDOM);
  },
 /**
  * Returns the HTML for a Node for the Palette Hierarchies.  nodeName is used both as a visual node label and for the text to be
  * inserted into the Expression text area when the node is clicked.  Acceptable values for nodeType are "func", "pv"
  * (process variable), and "acp" (activity class parameter).  Automatically adds parsing syntax for special characters.
  * @since 4.0
  */
  createPaletteNodeHTML: function(nodeName, nodeType, onClickExpr) {
    var imageURL, onclick, onmouseover;
    if(nodeType == "func") {
      imageURL = "components/expeditor/img/fx.gif";
      onmouseover = "AEE.showFunc('" + JSEscape(nodeName) +"')";
      onclick = "AEE.insertIntoExpression('";
      onclick += onClickExpr ? JSEscape(onClickExpr) : JSEscape(nodeName) + "()";
      onclick += onClickExpr ? "')" : "', true)";
    } else{
      imageURL = "components/expeditor/img/var.gif";
      var insertText = onClickExpr ? onClickExpr : this.encodeIdentifier(nodeName, nodeType);
      onclick = "AEE.insertIntoExpression('" + JSEscape(insertText) + "');";
    }
    var shortName = nodeName.replace(/^.*\>/,"");
    var html = HIERARCHY.createNodeHTML(shortName, imageURL, onclick, onmouseover);
    return html;
  },
  /**
  *  Returns the HTML for a complex webservices node
  */
  createComplexACPHtml: function(acpNameArray,index,paletteHTMLArray){
    var imageURL, onclick, onmouseover;
    var rootName;
    if(acpNameArray[index].indexOf("in_")!=-1){
      rootName = acpNameArray[index].substr(3,acpNameArray[index].indexOf('>')-3);
    }else{
      rootName = acpNameArray[index].substr(4,acpNameArray[index].indexOf('>')-4);
    }
    var cur = 0;
    var lastACP = null;
    var inputArray = new Array();
    inputArray[0] = new Array();
    var dispName =  rootName ;
    inputArray[0][0] = '<a title="'+dispName+'">'+dispName+'</a>';
    var x = index;
    for(x; x < acpNameArray.length; x++){
       var name = acpNameArray[index+(x-index)].split(/<|>/);
       var fname = null;
       if(name[0].indexOf("in_")!=-1){
         fname = name[0].substr(3,acpNameArray[index+(x-index)].indexOf('>')-3);
       }else{
         fname = name[0].substr(4,acpNameArray[index+(x-index)].indexOf('>')-4);
       }
       if(fname != rootName) break;
       if(name.length-2 != 0 && name[name.length-2] != lastACP){
          lastACP = name[name.length-2];
          cur = inputArray.length;
          inputArray[inputArray.length] = new Array();
          var dispName = name[name.length-2];
          inputArray[cur][inputArray[cur].length] = '<a class="'+dispName+'">'+dispName+'</a>';
       }else{
          var dispName = name[name.length-1];
          imageURL = "components/expeditor/img/var.gif";
          insertText = this.encodeIdentifier(acpNameArray[index+(x-index)], 'ac');
          onclick = "AEE.insertIntoExpression('" + JSEscape(insertText) + "');";
          inputArray[cur][inputArray[cur].length] = '<a title="'+dispName+'" onclick="'+onclick+'" ><img src="' + rewriteURL(imageURL) + '" alt="' + dispName + '"/> ' + dispName + '</a>';
       }
    }
    for(var y = 1; y < inputArray.length;y++){
      inputArray[0][inputArray[0].length] = inputArray[y];
    }
    paletteHTMLArray[paletteHTMLArray.length] = inputArray[0];
    return x;
  },

/**
  * Encodes PV/ACP Names with special characters so that the Expression parser will understand them.  PV/ACP Name should be
  * completely unprocessed (no ! or pv! prefix).  Valid types are 'pv' and 'ac'.
  * @since 4.0
  */
 encodeIdentifier: function(identifierName, type) {
    var encoded = identifierName.replace(/&#39;/g, "''");
    var isFirstCharLetter = /[a-zA-Z]/.test(encoded.charAt(0));
    var needsQuoteWrapper = /\W/.test(encoded) || (! isFirstCharLetter);
    encoded = "!" + encoded;
    if(type!='pv') encoded = type + encoded;
    if(needsQuoteWrapper) encoded = "'" + encoded + "'";
    return encoded;
  },
 /**
  * Returns the continaer div for the Expression Editor.
  * @since 4.0
  */
  getEditorDOM: function (id) {
    var div = document.createElement("DIV"); {
      div.className = "asiExpressionEditor";
      div.id = id;
      div = document.body.appendChild(div);
      div.innerHTML = asi.WAIT_PICKER;
    }
    return div;
  },
 /**
  * Returns the HTML for a toolbar button for the Expression Editor.  'onclick' is the JavaScript onclick event when you click on
  * the button.  'imageName' is the name of the gif for the button; it must be located in components/expeditor/img/.  'alt' is
  * onHover text for 508 compliance.
  * @since 4.0
  */
  getToolbarButtonDOM: function (onclick, imageName, alt) {
    var toolbarAnchor = document.createElement('A');{
      toolbarAnchor.onclick = onclick;
    }
    var toolbarImg = document.createElement('IMG'); {
      toolbarImg.src = rewriteURL("components/expeditor/img/" + imageName + ".gif");
      toolbarImg.alt = alt;
      toolbarImg.onmousedown = "this.style.borderStyle = 'inset'";
      toolbarImg.onmouseout = "this.style.borderStyle = 'outset'";
      toolbarImg.onmouseup = "this.style.borderStyle = 'outset'";
    }
    toolbarAnchor.appendChild(toolbarImg);
    return(toolbarAnchor);
  },
 /**
  * The remaining functions perform validation.  First the Expression is lexed into its constituent parts.  For example, sum(a+b)
  * becomes [sum,(,a,+,b].  This is where illegal characters such as _varName are caught.
  * If the Expression passes through the lexer, this stack of tokens is parsed.  The parser checks that tokens occur in the correct
  * order; this is where sum(a++b) or oops(doh[nonono)ohgod] would be caught.
  * @since 4.0
  */
  parser: function(valueStack,typeStack,lexemeTypes){
    function parser01(runner){
      function parser01a(runner){
        var op = valueStack[runner];
        if((op == '=' || op == '!=') && (runner + 1) < tokenCount)
          return parser01a(parser02(runner+1));
        return runner;
      }
      return parser01a(parser02(runner));
    }
    function parser02(runner){
      function parser02a(runner){
        var op = valueStack[runner];
        if((op == '>' || op == '>=') && (runner+1) < tokenCount)
          return parser02a(parser03(runner+1));
        return runner;
      }
      return parser02a(parser03(runner));
    }
    function parser03(runner){
      function parser03a(runner){
        var op = valueStack[runner];
        if((op == '<' || op == '<=') && (runner+1) < tokenCount)
          return parser03a(parser1(runner+1));
        return runner;
      }
      return parser03a(parser1(runner));
    }
    function parser1(runner){
      function parser1a(runner){
        var op = valueStack[runner];
        if((op == '+' || op == '-' || op == '&') && (runner+1) < tokenCount)
          return parser1a(parser2(runner+1));
        return runner;
      }
      return parser1a(parser2(runner));
    }
    function parser2(runner){
      function parser2a(runner){
        var op = valueStack[runner];
        if((op == '*' || op == '/') && (runner+1) < tokenCount)
          return parser2a(parser3(runner+1));
        return runner;
      }
      return parser2a(parser3(runner));
    }
    function parser3(runner){
      function parser3a(runner){
        var op = valueStack[runner];
        if((op == '^' || op == '.') && (runner+1) < tokenCount)
          return parser3a(parser4(runner+1));
        return runner;
      }
      return parser3a(parser4(runner));
    }
    function parser4(runner){
      function parser4a(runner){
        var op = valueStack[runner];
        if(op == '-' && (runner+1) < tokenCount)
          return parser4a(parser5(runner+1));
        return runner;
      }
      var op = valueStack[runner];
      if(op == '-' && (runner+1) < tokenCount)
        return parser4a(parser5(runner+1));
      return parser5(runner);
    }
    function parser5(runner){
      runner = parser6(runner);
      var op = valueStack[runner];
      if(op == '%')
        return runner+1;
      return runner;
    }
    function parser6(runner){
      var value = valueStack[runner];
      var type = typeStack[runner];
      if(value == '{'){
        runner = arraylit(runner);
        if(valueStack[runner] == '[')
          runner=indexlit(runner);
        return runner;
      } else if (value == '('){
        runner = parser01(runner+1);
        if(runner == tokenCount){
          exceptions.push('There is a parenthesized subexpression that is invalid.');
          return tokenCount;
        }
        if(valueStack[runner] != ')'){
          exceptions.push('There is a missing right parenthesis.');
          return tokenCount;
        }
        return runner+1;
      } else if (type == lexemeTypes.TOKEN){
        if(value == '.')
          return runner+1;
        else{
          exceptions.push('There is an misplaced or extra operator in the Expression.');
          return tokenCount;
        }
      } else if (type == lexemeTypes.STRING){
        return runner+1;
      } else if (type == lexemeTypes.IDENTIFIER){
        if((runner+1) < tokenCount){
          var ap = valueStack[runner+1];
          if(ap == '[') return index(runner+1);
          if(ap == '(') return func(runner+1);
        }
        return runner+1;
      } else
        return runner+1;
    }
    function index(runner){
      return indexlit(runner);
    }
    function func(runner){
      runner = funclit(runner);
      if(runner<tokenCount && valueStack[runner] == '[')
        runner = indexlit(runner);
      return runner;
    }
    function indexlit(runner){
      var op = valueStack[runner];
      if(op == ']')
        return runner+1;
      if(op!='[' && op!=','){
        exceptions.push(op + ' is an invalid character for separating indices.');
        return tokenCount;
      }
      runner += 1;
      runner = parser01(runner);
      return indexlit(runner);
    }
    function funclit(runner){
      var op = valueStack[runner];
      if(op == ')')
        return runner+1;
      if(op!='(' && op!=','){
        exceptions.push(op + ' is an invalid character for separating function parameters.');
        return tokenCount;
      }
      runner += 1;
      if((op=='(' && valueStack[runner] == ')'))
        return runner+1;
      runner = parser01(runner);
      return funclit(runner);
    }
    function arraylit(runner){
      var op = valueStack[runner];
      if(op == '}')
        return runner+1;
      if(op!='{' && op!=','){
        exceptions.push(op + ' is an invalid character for separating items in an array.');
        return tokenCount;
      }
      runner += 1;
      runner = parser01(runner);
      return arraylit(runner);
    }
    var runner = 0;
    var tokenCount = valueStack.length;
    var exceptions = new Array;
    for(var i=0; i<tokenCount; i++)
    {
      var currentItem = valueStack[i];
      if(currentItem instanceof Array)
        valueStack[i] = currentItem[1];
    }
    if(valueStack.length > 2 && typeStack[0]==lexemeTypes.IDENTIFIER  && valueStack[1].search(':') != -1 && typeStack[1]==lexemeTypes.TOKEN)
      runner += 2;
    runner = parser01(runner);
    if(runner < tokenCount){
      asi.alert('The Expression is either open ended or else it contains an extra operator.');
      return false;
    }
    if(exceptions.length > 0)
      asi.alert(exceptions[0]);
    else
      return true;
  },
  lexer: function(expression){
    var p = b = k = 0;
    var valueStack = new Array;
    var typeStack = new Array;
    var exceptions = new Array;
    var tokens = new Array;
    var lexemeTypes = new Array;

    tokens[0] = "(";
    tokens[1] = ")";
    tokens[2] = "[";
    tokens[3] = "]";
    tokens[4] = "{";
    tokens[5] = "}";
    tokens[6] = "+";
    tokens[7] = "-";
    tokens[8] = ".";
    tokens[9] = "*";
    tokens[10] = "/";
    tokens[11] = "^";
    tokens[12] = "%";
    tokens[13] = "&";

    tokens[14] = "<";
    tokens[15] = "<=";
    tokens[16] = ">";
    tokens[17] = ">=";
    tokens[18] = "=";
    tokens[19] = "<>";
    tokens[20] = ",";
    tokens[21] = "~";

    tokens[22] = ":";
    tokens[23] = "+:";
    tokens[24] = "-:";
    tokens[25] = "*:";
    tokens[26] = "/:";
    tokens[27] = "^:";
    tokens[28] = "&:";

    tokens[29] = "~";
    tokens[30] = ",";

    lexemeTypes.TOKEN = "token";
    lexemeTypes.STRING = 'string';
    lexemeTypes.IDENTIFIER = 'identifier';
    lexemeTypes.FLOAT = 'float';
    lexemeTypes.INTEGER = 'integer';

    if (expression.charAt(0)=="=")
      expression = expression.substring(1,expression.length);
    while(expression.length > 0){
      var c = this.lexerToken(expression,tokens);
      var type = lexemeTypes.TOKEN;
      while(true){
        if (c!=false) {
          switch(c){
            case '(':
              p += 1;
              break;
            case ')':
              p -= 1;
              if(p<0) exceptions.push('Unmatched close parenthesis )');
              break;
            case '[':
              k += 1;
              break;
            case ']':
              k -= 1;
              if(k<0) exceptions.push('Unmatched close bracket )');
              break;
            case '{':
              b += 1;
              break;
            case '}':
              b -= 1;
              if(b<0) exceptions.push('Unmatched close brace )');
              break;
          }
          break;
        }
        else c = this.lexerString(expression,exceptions);
        if (c!=false){
          type = lexemeTypes.STRING;
          break;
        }
        else c = this.lexerIdentifier(expression,exceptions,tokens);
        if (c!=false){
          type = lexemeTypes.IDENTIFIER;
          break;
        }
        else c = this.lexerFloat(expression);
        if (c!=false){
          type = lexemeTypes.FLOAT;
          break;
        }
        else c = this.lexerInt(expression);
        if (c != null){
          type = lexemeTypes.INTEGER;
          break;
        }
        else{
          asi.alert("The character '" + expression.charAt(0) + "' could not be recognized in the given context.");
          return false;
        }
      }
      expression = expression.substring(c ? c.toString().length : 1,expression.length);
      if(c){
        if(type==lexemeTypes.STRING){
          c = c.slice(1,-1);
          c = c.replace(/""/g,'"');
        }
        if(type==lexemeTypes.IDENTIFIER){
          if(c.indexOf("'") == 0){
            c = c.slice(1,-1);
            c = c.replace(/''/g,"'");
          }
          if (expression.charAt(0) == "(") {
            if (c.charAt(0)=='@') c = c.substring(1,c.length);
            c = new Array("function",c);
          }
          else{
            if(c.indexOf("!") == -1) c = "pv!" + c;
            else c = c.replace(/^!/,"pv!");
            var bangLocation = c.indexOf('!');
            c = new Array(c.substring(0,bangLocation),c.substring(bangLocation+1,c.length));
          }
        }
        valueStack.push(c);
        typeStack.push(type);
      }
    }
    if(p>0) exceptions.push("There is an unmatched open parenthesis");
    if(b>0) exceptions.push("There is an unmatched open brace");
    if(k>0) exceptions.push("There is an unmatched open bracket");
    if(exceptions.length > 0)
      asi.alert("The Expression has the following problems:<br/>"+exceptions.join("<br/>"));
    else
      return this.parser(valueStack,typeStack,lexemeTypes);
  },
  lexerToken: function(expression,tokens){
    if(expression.length > 1){
      var c2 = expression.substr(0,2);
      if (c2.search(/^\.\d/) == 0) return false;
      for(var i=0; i < tokens.length; i++){
        if(c2 == tokens[i])
          return c2;
      }
      switch(c2){
        case '><' :
          c2 = '<>';
          return c2;
        case '=<' :
          c2 = '>=';
          return c2;
        case '=<' :
          c2 = '<=';
          return c2;
        case '**' :
          c2 = '^';
          return c2;
      }
    }
    var c = expression.substr(0,1);
    switch(c){
      case ' ' :
      case '\t' :
      case '\n' :
      case '\r' :
      return null;
    }
    for(var i=0; i < tokens.length; i++){
      if(c == tokens[i])
        return c;
    }
    return false;
  },
  lexerString: function(expression,exceptions){
    var c = expression.substr(0,1);
    if (c == '"'){
      var i=1;
      while(true) {
        i = expression.indexOf('"', i);
        if (i == -1){
          exceptions.push('There is an unmatched double quote');
          return expression;
        }
        if(expression.charAt(i+1) == '"') i+=2;
        else return expression.substring(0,i+1);
      }
    }
    return false;
  },
  lexerIdentifier: function(expression,exceptions,tokens){
    var c = expression.substr(0,1);
    if (c.match(/\d/)) return false;
    if (c == "'"){
      var i=1;
      while(true) {
        i = expression.indexOf("'", i);
        if (i == -1){
          exceptions.push('There is an unmatched single quote');
          return expression;
        }
        if(expression.charAt(i+1) == "'") i+=2;
        else return expression.substring(0,i+1);
      }
    } else if (expression.search(/^[!@]?[a-zA-Z]+/) == 0){
      var tokenLocation = expression.search(/[\+\-\*\/\^%&\,<>=:~\(\)\[\]\{\}\.]/ , 1);
      if (tokenLocation == -1){
        return expression;
      }
      else return expression.substring(0,tokenLocation);
    }
    return false;
  },
  lexerFloat: function(expression){
    var c = expression.match(/^\d*\.\d+[\D]/);
    if(c != null) c = c.toString().slice(0,-1);
    else c = expression.match(/^\d*\.\d+$/);
    if(c) return c;
    return false;
  },
  lexerInt: function(expression){
    var c = expression.match(/^\d+[\D]/);
    if(c != null) c = c.toString().slice(0,-1);
    else c = expression.match(/^\d+$/);
    if(c != null) return c;
    return null;
  }
}
/*********************************
 EXPRESSION EDITOR COMPONENT
*********************************/
function HTMLArea(id, expEdit, onblur, inDesigner,PVNames,ACPNames){
  _editor_url = rewriteURL('/components/htmlarea/htmlarea/');                     // URL to htmlarea files
  var win_ie_ver = parseFloat(navigator.appVersion.split("MSIE")[1]);
  if (navigator.userAgent.indexOf('Mac')        >= 0) { win_ie_ver = 0; }
  if (navigator.userAgent.indexOf('Windows CE') >= 0) { win_ie_ver = 0; }
  if (navigator.userAgent.indexOf('Opera')      >= 0) { win_ie_ver = 0; }
  var isValidBrowser = false;
  if (win_ie_ver >= 5.5) {
    isValidBrowser = true;
  }
  this.stylesheets = ['/components/htmlarea/css/htmlArea.css'];
  this.className = 'htmlArea';
  this.type = "HTMLArea";
  this.component = Component;
  this.component(id, this.stylesheets, this.type);
  init(id, inDesigner,PVNames,ACPNames);
    
  function init(id,inDesigner,PVNames,ACPNames) {
    var config=new Object();
    config.inDesigner = inDesigner;
    config.PVNames = PVNames;
    config.ACPNames = ACPNames;
    config.width = "100%";
    config.height = "150px";
    config.bodyStyle = 'background-color: white;font-family: Arial,Verdana,Tahoma,Helvetica,Times; font-size:x-small; color: #333333;';
    config.debug = 0;
    config.id = id;
    config.imgURL = rewriteURL('/components/htmlarea/htmlarea/images/');
    if (expEdit) //show expression editor button
    {  config.toolbar = [['fontname', 'fontsize', 'forecolor', 
                        'bold','italic','underline','separator',
                       //'strikethrough','subscript','superscript','separator',
                       'OrderedList','UnOrderedList','separator',
                       //'InsertTable','HtmlMode','separator',
                       'HorizontalRule','CreateLink','InsertImage', 'separator',
                       'custom1', 'custom2','custom3','custom4','custom5','custom6','custom7','custom8', 'custom9']
                     ];
    }
    else
    {
        config.toolbar = [ ['fontname', 'fontsize', 'forecolor',
                            'bold','italic','underline'//,'separator',
                               //'strikethrough','subscript','superscript','separator',
                               //'OrderedList','UnOrderedList','separator',
                               //'InsertTable','HtmlMode','separator',
                               //'HorizontalRule','CreateLink','InsertImage', 'separator',
                               //'custom1', 'custom2','custom3','custom4','custom5','custom6','custom7','custom8'
						] ];
    }
    getObject('expression_'+id).mode = '';
    getObject('textArea_' + id).value= getObject('text_' + id).value;
    if (isValidBrowser) {
      editor_generate('textArea_' + id, config);
      document.getElementById('toolbarbuttons_' + id).style.display = "";
    } else {
      document.getElementById('notSupportedMessage' + id).style.display = "";
    }

  }
  function getExpValue(expString) {
    if(expString == "") return expString;
    var pos = 0;
    var first = true;
    var temp;
    var str = "=concatenate("; 
    while(expString.search("<IMG class=expImg")  != -1){
      var i = expString.search("<IMG class=expImg");
      if(i > pos){
        if(!first){
          str += ',';
        }else{
          first = false;
        }
        str+="\"";
        temp = expString.substr(pos,i-pos).replace(/"/g,'""').unescapeHTML();
        str+= temp.replace(/&nbsp;/g, " ");
        str+="\"";
        expString = expString.substr(i,expString.length);
      }
      var beginAltPos = expString.search("alt=")+4;
      var endAltPos = 0;
      expString = expString.substr(beginAltPos,expString.length);
      if(!first){
        str += ',';
      }else{
        first = false;
      }
      var c = expString.charAt(0);
      expString = expString.substr(1,expString.length);
      if(c == '"'){
        endAltPos = expString.search('"');
      }else if(c == "'"){
        endAltPos = expString.search("'");
      }else{
        expString = c+expString;
        endAltPos = expString.search(" ");
      }
      temp = expString.substr(0,endAltPos).unescapeHTML();
      str+= temp.replace(/&nbsp;/g, " ");
      expString = expString.substr(endAltPos, expString.length);
      var endImg = expString.search('>');
      expString = expString.substr(endImg+1,expString.length);
    }
    if(expString.length > 0 && expString != ''){
      if(!first){
        str += ',';
      }else{
        first = false;
      }
      str += "\"";
      temp = expString.replace(/"/g,'""').unescapeHTML();
      str += temp.replace(/&nbsp;/g, " ");
      str +="\"";
    }
    str += ")";

    getStringValue(str);
    return str;
 }

 function getExp(expValue) {
    var str = "<img class=\"expImg\" src=\"" + rewriteURL('/components/htmlarea/htmlarea/images/openEditor.gif') + "\"";
    if(expValue.charAt(0) == '=') expValue = expValue.substr(1,expValue.length-1);
    str += " alt=\"" + expValue + "\"";
    str += " id=\"expImgId\"/>";
    return str;
 }
  function getStringValue(expString){
      //Build new string, stripping out quotes and commas and opening concatenate=(
      //send expressions to getExp(expValue)
  
      expString = expString.replace(/^=concatenate/,"");
      var quotedString = '';
      var exprTerm = '';
      var retVal = '';
      var p = 0;
      var q = false;
      var stringOnly = false;

      for(var x = 0; x < expString.length; x++){
        var c = expString.charAt(x);
        exprTerm += c;
        if(!q){
          //non-quote handler
          if(c=='('){
            if(p==0){
              // start of first term
              exprTerm = '';
            }
            p++;
          }else if(c==')'){
            p--;
            if(p==0){
              //end of last term
              if(stringOnly){
                retVal += quotedString.escapeHTML();
              }else{
                retVal+= "<img class=\"expImg\" src=\""+rewriteURL('/components/expeditor/img/openEditor.gif')+"\" alt=\""+exprTerm.substr(0,exprTerm.length-1)+"\" />";  
              }
            }
          }else if(c=='\"'){
            // beginning of text string
            quotedString = '';
            if(exprTerm == '\"'){
              stringOnly = true;
            }
            if(x<expString.length-1 && expString.charAt(x+1)=='\"'){
              //escaped 
              x++; // skip over quote; for's i++ will skip over _this_ quote
            }else{
              q = true;
            }
          }else if(p==1 && c==','){
            if(stringOnly){
              retVal += quotedString.escapeHTML();
            }else{
              retVal+= "<img class=\"expImg\" src=\""+rewriteURL('/components/expeditor/img/openEditor.gif')+"\" alt='"+exprTerm.substr(0,exprTerm.length-1)+"' />";  
            }
            quotedString = '';
            exprTerm = '';
            stringOnly = false;
          }else{
            stringOnly = false;
          }
        }else{
          //quoted text handler
          if(c=='\"'){
            //either an end quote or an escaped quote
            if(x<expString.length-1 && expString.charAt(x+1)=='\"'){
              //escaped 
              x++; // skip over quote; for's i++ will skip over _this_ quote
            }else{
              // end of string
              q= false;
              continue;
            }
          }
          quotedString += c;
        }
      }
      return retVal;
    }
this.getVal = function(f){
  return getExpValue(f.value);
}
this.saveData = function(f){
  var expValue = getExpValue(f.value);
  asi.alert(HTMLEscape(expValue));
}
function submitForm(id){
  var contents = getObject('_'+this.id+'_editor').document.body.createTextRange().htmlText;
  f.value = contents;
}
this.updateValue = function(expField){
  if(expField.mode == 'edit') {
    expField.source.alt = expField.value;
    expField.value = '';
    expField.mode = '';
  }else{
    var htmlArea = getObject('textArea_'+ this.id);
    var sel = htmlArea.sel;
    var expString = getExp(HTMLEscape(expField.value));
    if(htmlArea.sel != null) {
      sel.pasteHTML(expString);
    }
    else htmlArea.value += expString;
    expField.value = '';
  }
  editor_focus(getObject('_textArea_' + this.id + '_editor'));
}

/**
 * Opens the HTMLArea.
 * @since 4.1
 */
  this.show = function () {
    if(getObject(this.id)) return getObject(this.id);
    backgroundPage('/p_mini/htmlarea.do?$e=asiDialog');
  }

  //this.show();
  }

//
// htmlArea v2.02 - Copyright (c) 2002 interactivetools.com, inc.
// This copyright notice MUST stay intact for use (see license.txt).
//
// A free WYSIWYG editor replacement for <textarea> fields.
// For full source code and docs, visit http://www.interactivetools.com/
//

// write out styles for UI buttons
document.write('<style type="text/css">\n');
document.write('.asiToolbar img.btn { border: 1px solid #d5e5f3;  padding: 2px 4px;  white-space: nowrap;  margin: 2px 2px 2px 0px !important;}\n');
document.write('.btnOver { width: 14px;  height: 14px;  padding: 1px !important;   margin: 2px 3px 3px 0!important;  border: 1px solid #999 !important;   border-left: 0px;   border-top: 0px; background-color:white;}\n');
document.write('.btnDown { width: 14px;  height: 14px;  padding: 1px !important;   margin: 2px 3px 3px 0!important;  border: 1px solid #999 !important;   border-left: 0px;   border-top: 0px; }\n');
document.write('.btnNA   { width: 14px; height: 14px; padding: 1px !important;   margin: 2px 3px 3px 0!important; border: 0px solid buttonface; filter: alpha(opacity=25); }\n');
document.write('.cMenu     { background-color: threedface; color: menutext; cursor: Default; font-family: MS Sans Serif; font-size: 8pt; padding: 2 12 2 16; }');
document.write('.cMenuOver { background-color: highlight; color: highlighttext; cursor: Default; font-family: MS Sans Serif; font-size: 8pt; padding: 2 12 2 16; }');
document.write('.cMenuDivOuter { background-color: threedface; height: 9 }');
document.write('.cMenuDivInner { margin: 0 4 0 4; border-width: 1; border-style: solid; border-color: threedshadow threedhighlight threedhighlight threedshadow; }');
document.write('</style>\n');


/* ---------------------------------------------------------------------- *\
  Function    : editor_defaultConfig
  Description : default configuration settings for wysiwyg editor
\* ---------------------------------------------------------------------- */

function editor_defaultConfig(objname) {

this.version = "2.02"

this.width =  "auto";
this.height = "auto";
this.bodyStyle = 'background-color: #FFFFFF; font-family: "Verdana"; font-size: x-small;';
this.imgURL = _editor_url + 'images/';
this.debug  = 0;

this.replaceNextlines = 0; // replace nextlines from spaces (on output)
this.plaintextInput = 0;   // replace nextlines with breaks (on input)

this.toolbar = [
    ['fontname'],
    ['fontsize'],
//    ['fontstyle'],
//    ['linebreak'],
    ['bold','italic','underline','separator'],
//  ['strikethrough','subscript','superscript','separator'],
    ['justifyleft','justifycenter','justifyright','separator'],
    ['OrderedList','UnOrderedList','Outdent','Indent','separator'],
    ['forecolor','backcolor','separator'],
    ['HorizontalRule','Createlink','InsertImage','InsertTable','htmlmode','separator'],
    ['InserDoc','separator'],
//  ['custom1','custom2','custom3','separator'],
    ['popupeditor','about']];

this.fontnames = {
    "Arial":           "Arial, Helvetica, Sans-Serif",
    "Courier New":     "Courier New, Courier, Mono",
    "Georgia":         "Georgia, Times New Roman, Times, Serif",
    "Tahoma":          "Tahoma, Arial, Helvetica, Sans-Serif",
    "Times New Roman": "Times New Roman, Times, Serif",
    "Verdana":         "Verdana, Arial, Helvetica, Sans-Serif",
    "Impact":          "Impact",
    "Wingdings":       "Wingdings"};

this.fontsizes = {
    "small":  "2",
    "medium": "4",
    "large": "7"
  };

//this.stylesheet = "http://www.domain.com/sample.css"; // full URL to stylesheet

this.fontstyles = [     // make sure these exist in the header of page the content is being display as well in or they won't work!
//    { name: "headline",     className: "headline",  classStyle: "font-family: arial black, arial; font-size: 28px; letter-spacing: -2px;" },
//    { name: "arial red",    className: "headline2", classStyle: "font-family: arial black, arial; font-size: 12px; letter-spacing: -2px; color:red" },
//    { name: "verdana blue", className: "headline4", classStyle: "font-family: verdana; font-size: 18px; letter-spacing: -2px; color:blue" },
];

this.btnList = {
    // buttonName:    commandID,               title,                onclick,                   image,             
    "bold":           ['Bold',                 'Bold',               'editor_action(this.id)',  'ed_format_bold.gif'],
    "italic":         ['Italic',               'Italic',             'editor_action(this.id)',  'ed_format_italic.gif'],
    "underline":      ['Underline',            'Underline',          'editor_action(this.id)',  'ed_format_underline.gif'],
    "strikethrough":  ['StrikeThrough',        'Strikethrough',      'editor_action(this.id)',  'ed_format_strike.gif'],
    "subscript":      ['SubScript',            'Subscript',          'editor_action(this.id)',  'ed_format_sub.gif'],
    "superscript":    ['SuperScript',          'Superscript',        'editor_action(this.id)',  'ed_format_sup.gif'],
    "justifyleft":    ['JustifyLeft',          'Justify Left',       'editor_action(this.id)',  'ed_align_left.gif'],
    "justifycenter":  ['JustifyCenter',        'Justify Center',     'editor_action(this.id)',  'ed_align_center.gif'],
    "justifyright":   ['JustifyRight',         'Justify Right',      'editor_action(this.id)',  'ed_align_right.gif'],
    "orderedlist":    ['InsertOrderedList',    'Ordered List',       'editor_action(this.id)',  'ed_list_num.gif'],
    "unorderedlist":  ['InsertUnorderedList',  'Bulleted List',      'editor_action(this.id)',  'ed_list_bullet.gif'],
    "outdent":        ['Outdent',              'Decrease Indent',    'editor_action(this.id)',  'ed_indent_less.gif'],
    "indent":         ['Indent',               'Increase Indent',    'editor_action(this.id)',  'ed_indent_more.gif'],
    "forecolor":      ['ForeColor',            'Font Color',         'editor_action(this.id)',  'ed_color_fg.gif'],
    "backcolor":      ['BackColor',            'Background Color',   'editor_action(this.id)',  'ed_color_bg.gif'],
    "horizontalrule": ['InsertHorizontalRule', 'Horizontal Rule',    'editor_action(this.id)',  'ed_hr.gif'],
    "createlink":     ['CreateLink',           'Insert Web Link',    'editor_action(this.id)',  'link.gif'],
    "insertimage":    ['InsertImage',          'Insert Image',       'editor_action(this.id)',  'ed_image.gif'],
    "inserttable":    ['InsertTable',          'Insert Table',       'editor_action(this.id)',  'insert_table.gif'],
    "htmlmode":       ['HtmlMode',             'View HTML Source',   'editor_setmode(\''+objname+'\')', 'ed_html.gif'],
    "popupeditor":    ['popupeditor',          'Enlarge Editor',     'editor_action(this.id)',  'fullscreen_maximize.gif'],
    "about":          ['about',                'About this editor',  'editor_about(\''+objname+'\')',  'ed_about.gif'],

    // Add custom buttons here:
    "custom1":           ['custom1',         'Insert Document from Collaboration Center',  'editor_action(this.id)',  'insert_document.gif'],
    "custom2":           ['custom2',         'Insert Image from Collaboration Center',  'editor_action(this.id)',  'temp_collab_img.gif'],
    "custom3":           ['custom3',         'Insert Folder from Collaboration Center',  'editor_action(this.id)',  'folder.gif'],
    "custom4":           ['custom4',         'Insert Knowledge Center from Collaboration Center','editor_action(this.id)','insert_knowledgecenter.gif'],
    "custom5":           ['custom5',         'Insert a link to an internal page','editor_action(this.id)','sendlink.gif'],
    "custom6":           ['custom6',         'Insert Discussion Forum','editor_action(this.id)','insert_forum.gif'],
    "custom7":           ['custom7',         'Insert Group','editor_action(this.id)','group.gif'],
	  "custom8":           ['custom8',         'Insert a user','editor_action(this.id)','user.gif'],
    "custom9":           ['custom9',         'Insert an Expression', 'editor_action(this.id)', 'openEditor.gif'],
   // end: custom buttons

    "help":           ['showhelp',             'Help using editor',  'editor_action(this.id)',  'ed_help.gif']};


}

/* ---------------------------------------------------------------------- *\
  Function    : editor_generate
  Description : replace textarea with wysiwyg editor
  Usage       : editor_generate("textarea_id",[height],[width]);
  Arguments   : objname - ID of textarea to replace
                w       - width of wysiwyg editor
                h       - height of wysiwyg editor
\* ---------------------------------------------------------------------- */


function editor_generate(objname,userConfig, inDesigner) {

  // Default Settings
  var config = new editor_defaultConfig(objname);
  if (userConfig) { 
    for (var thisName in userConfig) {
if (typeof userConfig[thisName] == 'function') continue;
      if (userConfig[thisName]) { config[thisName] = userConfig[thisName]; }
    }
  }
  document.all[objname].config = config;                  // store config settings
 
  // set size to specified size or size of original object
  var obj    = document.all[objname];
  if (!config.width || config.width == "auto") {
    if      (obj.style.width) { config.width = obj.style.width; }      // use css style
    else if (obj.cols)        { config.width = (obj.cols * 8) + 22; }  // col width + toolbar
    else                      { config.width = '100%'; }               // default
  }
  if (!config.height || config.height == "auto") {
    if      (obj.style.height) { config.height = obj.style.height; }   // use css style
    else if (obj.rows)         { config.height = obj.rows * 17 }       // row height
    else                       { config.height = '200'; }              // default
  }

  var tblOpen  = '<table border=0 cellspacing=0 cellpadding=0 style="float: left;"  unselectable="on"><tr><td style="border: none; padding: 2px;"><nobr>';
  var tblClose = '</nobr></td></tr></table>\n';

  // build button toolbar

  var toolbar = '';
  var btnGroup, btnItem, aboutEditor;
  for (var btnGroup in config.toolbar) {
if (typeof config.toolbar[btnGroup] == 'function') continue;

    // linebreak
    if (config.toolbar[btnGroup].length == 1 &&
        config.toolbar[btnGroup][0].toLowerCase() == "linebreak") {
      toolbar += '<br clear="all">';
      continue;
    }

    //toolbar += tblOpen;
    for (var btnItem in config.toolbar[btnGroup]) {
if (typeof config.toolbar[btnGroup][btnItem] == 'function') continue;
      var btnName = config.toolbar[btnGroup][btnItem].toLowerCase();
      // fontname
      if (btnName == "fontname") {
        toolbar += '<select id="_' +objname+ '_FontName" onChange="editor_action(this.id)" unselectable="on" style="font-size: 11px;border: 1px solid #a0b6d0; font:arial;width: 120px;margin: 0 2 2 2;">';
        for (var fontname in config.fontnames) {
if (typeof config.fontnames[fontname] == 'function') continue;
          toolbar += '<option value="' +config.fontnames[fontname]+ '">' +fontname+ '</option>'
        }
        toolbar += '</select>';
        continue;
      }

      // fontsize
      if (btnName == "fontsize") {
        toolbar += '<select id="_' +objname+ '_FontSize" onChange="editor_action(this.id)" unselectable="on" style="font-size: 11px;border: 1px solid #a0b6d0; font:arial;width: 70px;margin: 0 2 2 2;">';
        for (var fontsize in config.fontsizes) {
if (typeof config.fontsizes[fontsize] == 'function') continue;
          toolbar += '<option value="' +config.fontsizes[fontsize]+ '">' +fontsize+ '</option>'
        }
        toolbar += '</select>\n';
        continue;
      }

      // font style
      if (btnName == "fontstyle") {
        toolbar += '<select id="_' +objname+ '_FontStyle" onChange="editor_action(this.id)" unselectable="on" style="margin: 1 2 0 0; font-size: 12px;">';
        + '<option value="">Font Style</option>';
        for (var i in config.fontstyles) {
if (typeof config.fontstyles[i] == 'function') continue;
          var fontstyle = config.fontstyles[i];
          toolbar += '<option value="' +fontstyle.className+ '">' +fontstyle.name+ '</option>'
        }
        toolbar += '</select>';
        continue;
      }

      // separator
      if (btnName == "separator") {
        toolbar += '<span style="border: 1px inset; width: 1px; font-size: 14px; height: 14px; margin: 0 3 0 3"></span>';
        continue;
      }

      // buttons
      var btnObj = config.btnList[btnName];
      if (btnName == 'linebreak') { alert("htmlArea error: 'linebreak' must be in a subgroup by itself, not with other buttons.\n\nhtmlArea wysiwyg editor not created."); return; }
      if (!btnObj) { alert("htmlArea error: button '" +btnName+ "' not found in button list when creating the wysiwyg editor for '"+objname+"'.\nPlease make sure you entered the button name correctly.\n\nhtmlArea wysiwyg editor not created."); return; }
      var btnCmdID   = btnObj[0];
      var btnTitle   = btnObj[1];
      var btnOnClick = btnObj[2];
      var btnImage   = btnObj[3];
      toolbar += '<a href="#" onclick="' +btnOnClick+ '" id="_' +objname+ '_' +btnCmdID+ '" style="width: 14px;  height: 14px;  padding: 1px !important;   margin: 2px 3px 3px 0!important;  border: 1px solid #999 !important;   border-left: 0px;   border-top: 0px;" ><img alt="' +btnTitle+ '"  style="padding: 2px; " src="' +config.imgURL + btnImage+ '" unselectable="on"></a>';
  
    } // end of button sub-group
    //toolbar += tblClose;
  } // end of entire button set

  // build editor
 var editor='';
 if(config.inDesigner){
     editor = '<div class="asiToolbar" id="htmlAreaToolbarDesigner" style="width:' +(config.width + 5) + ';" width=' + config.width + ' unselectable="on">\n'
    + toolbar
    + '</div>\n'
    + '<textarea ID="_' +objname + '_editor" style="width:' +config.width+ '; height:' +config.height+ '; margin-top: -1px; margin-bottom: -1px;" wrap=soft ></textarea>';
 }else{
     editor = '<div class="asiToolbar" id="htmlAreaToolbarPortal" style="padding: 1 0 4px 7; margin-left: 0px; border: 1px solid #999; border-bottom: 0px;width:"'+ config.width +'" unselectable="on">\n'
    + toolbar
    + '</div>\n'
    + '<textarea ID="_' +objname + '_editor" style="width:' +config.width+ '; height:' +config.height+ '; margin-top: -1px; margin-bottom: -1px;" wrap=soft ></textarea>';
 }
  // add context menu
  editor += '<div id="_' +objname + '_cMenu" style="position: absolute; visibility: hidden;"></div>';

  //  hide original textarea and insert htmlarea after it
  if (!config.debug) {
    document.all[objname].style.display = "none";
  } else {
    document.all[objname].style.display = "";
  }

  if (config.plaintextInput) {     // replace nextlines with breaks
    var contents = document.all[objname].value;
    contents = contents.replace(/\r\n/g, '<br>');
    contents = contents.replace(/\n/g, '<br>');
    contents = contents.replace(/\r/g, '<br>');
    document.all[objname].value = contents;
  }

  // insert wysiwyg
  document.all[objname].insertAdjacentHTML('afterEnd', editor)

  // convert htmlarea from textarea to wysiwyg editor
  editor_setmode(objname, 'init');

  // call filterOutput when user submits form
  for (var idx=0; idx < document.forms.length; idx++) {
    var r = document.forms[idx].attachEvent('onsubmit', function() { editor_filterOutput(objname); });
    if (!r) { alert("Error attaching event to form!"); }
  }

return true;

}

/* ---------------------------------------------------------------------- *\
  Function    : editor_action
  Description : perform an editor command on selected editor content
  Usage       :
  Arguments   : button_id - button id string with editor and action name
\* ---------------------------------------------------------------------- */

function editor_action(button_id) {
  // split up button name into "editorID" and "cmdID"
  var BtnParts = Array();
  BtnParts = button_id.split("_");
  var objname    = button_id.replace(/^_(.*)_[^_]*$/, '$1');
  var cmdID      = BtnParts[ BtnParts.length-1 ];
  var button_obj = document.all[button_id];
  var editor_obj = document.all["_" +objname + "_editor"];
  var config     = document.all[objname].config;
  // help popup
  if (cmdID == 'showhelp') {
    popup("/components/htmlarea/htmlarea/popups/editor_help.html", 'EditorHelp');
    return;
  }

  // popup editor
  if (cmdID == 'popupeditor') {
    popup("/components/htmlarea/htmlarea/popups/fullscreen.html?"+objname,
                'FullScreen',
                'toolbar=no,location=no,directories=no,status=yes,menubar=no,scrollbars=yes,resizable=yes,width=640,height=480');
    return;
  }

  // check editor mode (don't perform actions in textedit mode)
  if (editor_obj.tagName.toLowerCase() == 'textarea') { return; }

  var editdoc = editor_obj.contentWindow.document;

  
  
  editor_focus(editor_obj);

  // get index and value for pulldowns
  var idx = button_obj.selectedIndex;
  var val = (idx != null) ? button_obj[ idx ].value : null;

  if (0) {}   // use else if for easy cutting and pasting

  //
  // CUSTOM BUTTONS START HERE
  //

   // custom1
  else if (cmdID == 'custom1') {
    //if (editdoc.selection.createRange().text != "") { 
    //  var highlightedText = editdoc.selection.createRange(); 
    //} else { 
    //  var highlightedText = ""; 
    //} 
    var myText = showModalDialog(_editor_url + "popups/insert_docs.jsp", editdoc, "dialogHeight:40;dialogWidth:37;resizable: no; help: no; status: no; scroll: no; ");
    if(myText){ editor_insertHTML(objname, unescape(myText)); }
  }
  // Custom2
  else if (cmdID == 'custom2') {
    var myText = showModalDialog(_editor_url + "popups/insert_img.jsp",
                                 editdoc,      // str or obj specified here can be read from dialog as "window.dialogArguments"
                                 "dialogHeight:40;dialogWidth:37;resizable: no; help: no; status: no; scroll: no; ");
    if(myText){ editor_insertHTML(objname, unescape(myText)); }
  }
  // Custom3
  else if (cmdID == 'custom3') { 
    var myText = showModalDialog(_editor_url + "popups/insert_fol.jsp", editdoc, "dialogHeight:28;dialogWidth:32;resizable: no; help: no; status: no; scroll: no; ");
    if(myText){ editor_insertHTML(objname, unescape(myText)); }
  }
  // Custom4
  else if (cmdID == 'custom4') { 
    var myText = showModalDialog(_editor_url + "popups/insert_kc.jsp", editdoc, "dialogHeight:28;dialogWidth:32;resizable: no; help: no; status: no; scroll: no; ");
    if(myText){ editor_insertHTML(objname, unescape(myText)); }
  }
  // Custom5
  else if (cmdID == 'custom5') { 
    var myText = showModalDialog(_editor_url + "popups/insert_internallink.jsp", editdoc, "dialogHeight:28;dialogWidth:32;resizable: no; help: no; status: no; scroll: no; ");
    if(myText){ editor_insertHTML(objname, unescape(myText)); }
  }
  // Custom6
  else if (cmdID == 'custom6') { 
    var myText = showModalDialog(_editor_url + "popups/insert_forum.jsp", editdoc, "dialogHeight:28;dialogWidth:32;resizable: no; help: no; status: no; scroll: no; ");
    if(myText){ editor_insertHTML(objname, unescape(myText)); }
  }
  // Custom7
  else if (cmdID == 'custom7') { 
    var myText = showModalDialog(_editor_url + "popups/insert_group.jsp", editdoc, "dialogHeight:28;dialogWidth:32;resizable: no; help: no; status: no; scroll: no; ");
    if(myText){ editor_insertHTML(objname, unescape(myText)); }
  }
  // Custom8
  else if (cmdID == 'custom8') { 
    var myText = showModalDialog(_editor_url + "popups/insert_user.jsp", editdoc, "dialogHeight:28;dialogWidth:32;resizable: no; help: no; status: no; scroll: no; ");
    if(myText){ editor_insertHTML(objname, unescape(myText));
    }
  }
  // Custom9
    else if(cmdID =='custom9'){
      var expField = getObject('expression_' + document.all[objname].config.id);
      expField.value = '';
      expField.mode ='';
      var htmlArea = getObject(objname);
      htmlArea.sel = editor_getCursorSelection(objname);
    expField.name = objname;
    var myExpEditor = new ExpressionEditor(expField,config.PVNames,config.ACPNames);
  }
  //
  // END OF CUSTOM BUTTONS
  //

  // FontName
  else if (cmdID == 'FontName' && val) {
    editdoc.execCommand(cmdID,0,val);
  }

  // FontSize
  else if (cmdID == 'FontSize' && val) {
    editdoc.execCommand(cmdID,0,val);
  }

  // FontStyle (change CSS className)
  else if (cmdID == 'FontStyle' && val) {
    editdoc.execCommand('RemoveFormat');
    editdoc.execCommand('FontName',0,'636c6173734e616d6520706c616365686f6c646572');
    var fontArray = editdoc.all.tags("FONT");
    for (i=0; i<fontArray.length; i++) {
      if (fontArray[i].face == '636c6173734e616d6520706c616365686f6c646572') {
        fontArray[i].face = "";
        fontArray[i].className = val;
        fontArray[i].outerHTML = fontArray[i].outerHTML.replace(/face=['"]+/, "");
        }
    }
    button_obj.selectedIndex =0;
  }

  // fgColor and bgColor
  else if (cmdID == 'ForeColor' || cmdID == 'BackColor') {
    var oldcolor = _dec_to_rgb(editdoc.queryCommandValue(cmdID));
    var newcolor = showModalDialog(_editor_url + "popups/select_color.html", oldcolor, "dialogHeight: 12; dialogWidth: 15; resizable: no; help: no; status: no; scroll: no;");
    if (newcolor != null) { editdoc.execCommand(cmdID, false, "#"+newcolor); }
  }

  // execute command for buttons - if we didn't catch the cmdID by here we'll assume it's a
  // commandID and pass it to execCommand().   See http://msdn.microsoft.com/workshop/author/dhtml/reference/commandids.asp
  else {
    // subscript & superscript, disable one before enabling the other
    if (cmdID.toLowerCase() == 'subscript' && editdoc.queryCommandState('superscript')) { editdoc.execCommand('superscript'); }
    if (cmdID.toLowerCase() == 'superscript' && editdoc.queryCommandState('subscript')) { editdoc.execCommand('subscript'); }

    // insert link
    if (cmdID.toLowerCase() == 'createlink'){
      //editdoc.execCommand(cmdID,1);
      if (editdoc.selection.createRange().text != "") {
        var highlightedText = editdoc.selection.createRange().text;         
      } else {
        var highlightedText = "";       
      }
      var myText = showModalDialog(_editor_url + "popups/insert_hyperlink.jsp", highlightedText, "dialogHeight: 25; dialogWidth: 40; resizable: no; help: no; status: no; scroll: no;");            
      if (myText) { editor_insertHTML(objname, unescape( myText) ); }
    }

    // insert image
    else if (cmdID.toLowerCase() == 'insertimage'){
      var myText = showModalDialog(_editor_url + "popups/insert_image.jsp", editdoc, "dialogHeight: 40; dialogWidth: 40; resizable: no; help: no; status: no; scroll: no; ");
      if(myText){ editor_insertHTML(objname, unescape(myText)); }
    }
    // insert table
    else if (cmdID.toLowerCase() == 'inserttable'){
      showModalDialog(_editor_url + "popups/insert_table.html?"+objname,
                                 window,
                                 "resizable: yes; help: no; status: no; scroll: no; ");
    }

    // all other commands microsoft Command Identifiers
    else { editdoc.execCommand(cmdID); }
  }

  editor_event(objname);
  updateMiniRange(editor_obj.contentWindow.document.body);
}

/* ---------------------------------------------------------------------- *\
  Function    : editor_event
  Description : called everytime an editor event occurs
  Usage       : editor_event(objname, runDelay, eventName)
  Arguments   : objname - ID of textarea to replace
                runDelay: -1 = run now, no matter what
                          0  = run now, if allowed
                        1000 = run in 1 sec, if allowed at that point
\* ---------------------------------------------------------------------- */

function editor_event(objname,runDelay) {
  if(!document.all[objname]) return;
  var config = document.all[objname].config;
  var editor_obj  = document.all["_" +objname+  "_editor"];       // html editor object
  if (runDelay == null) { runDelay = 0; }
  var editEvent = editor_obj.contentWindow ? editor_obj.contentWindow.event : event;
  //var editdoc = editor_obj.contentWindow.document;
  
  if(editEvent && editEvent.srcElement && editEvent.srcElement.className == 'expImg'){
    var expField = getObject('expression_'+ document.all[objname].config.id)
    expField.value = editEvent.srcElement.alt;
     if(expField.value.charAt(0) != '=') expField.value = '='+expField.value;
    expField.mode = 'edit';
    expField.source = editEvent.srcElement;
    var expEditor = new ExpressionEditor(expField);
    return;
  }
  
  // catch keypress events
    if (editEvent && editEvent.keyCode) {
      var ord       = editEvent.keyCode;    // ascii order of key pressed
      var ctrlKey   = editEvent.ctrlKey;
      var altKey    = editEvent.altKey;
      var shiftKey  = editEvent.shiftKey;

      if (ord == 16) { return; }  // ignore shift key by itself
      if (ord == 17) { return; }  // ignore ctrl key by itself
      if (ord == 18) { return; }  // ignore alt key by itself

       // cancel ENTER key and insert <BR> instead
//       if (ord == 13 && editEvent.type == 'keypress') {
//         editEvent.returnValue = false;
//         editor_insertHTML(objname, "<br>");
//         return;
//       }

      if (ctrlKey && (ord == 122 || ord == 90)) {     // catch ctrl-z (UNDO)
//      TODO: Add our own undo/redo functionality
//        editEvent.cancelBubble = true;
        return;
      }
      if ((ctrlKey && (ord == 121 || ord == 89)) ||
          ctrlKey && shiftKey && (ord == 122 || ord == 90)) {     // catch ctrl-y, ctrl-shift-z (REDO)
//      TODO: Add our own undo/redo functionality
        return;
      }
    }

  // setup timer for delayed updates (some events take time to complete)
  if (runDelay > 0) { return setTimeout(function(){ editor_event(objname); }, runDelay); }

  // don't execute more than 3 times a second (eg: too soon after last execution)
  if (this.tooSoon == 1 && runDelay >= 0) { this.queue = 1; return; } // queue all but urgent events
  this.tooSoon = 1;
  setTimeout(function(){
    this.tooSoon = 0;
    if (this.queue) { editor_event(objname,-1); };
    this.queue = 0;
    }, 333);  // 1/3 second


  editor_updateOutput(objname);
  editor_updateToolbar(objname);

}

/* ---------------------------------------------------------------------- *\
  Function    : editor_updateToolbar
  Description : update toolbar state
  Usage       :
  Arguments   : objname - ID of textarea to replace
                action  - enable, disable, or update (default action)
\* ---------------------------------------------------------------------- */

function editor_updateToolbar(objname,action) {
  var config = document.all[objname].config;
  var editor_obj  = document.all["_" +objname+  "_editor"];

  // disable or enable toolbar

  if (action == "enable" || action == "disable") {
    var tbItems = new Array('FontName','FontSize','FontStyle');                           // add pulldowns
    for (var btnName in config.btnList) { 
if (typeof config.btnList[btnName] == 'function') continue;
      tbItems.push(config.btnList[btnName][0]); // add buttons
    } 
    for (var idxN in tbItems) {
if (typeof tbItems[idxN] == 'function') continue;
      var cmdID = tbItems[idxN].toLowerCase();
      var tbObj = document.all["_" +objname+ "_" +tbItems[idxN]];
      if (cmdID == "htmlmode" || cmdID == "about" || cmdID == "showhelp" || cmdID == "popupeditor") { continue; } // don't change these buttons
      if (tbObj == null) { continue; }
      var isBtn = (tbObj.tagName.toLowerCase() == "button") ? true : false;

      if (action == "enable")  { tbObj.disabled = false; if (isBtn) { tbObj.className = 'btn' }}
      if (action == "disable") { tbObj.disabled = true;  if (isBtn) { tbObj.className = 'btnNA' }}
    }
    return;
  }

  // update toolbar state

  if (editor_obj.tagName.toLowerCase() == 'textarea') { return; }   // don't update state in textedit mode
  var editdoc = editor_obj.contentWindow.document;

  // Set FontName pulldown
  var fontname_obj = document.all["_" +objname+ "_FontName"];
  if (fontname_obj) {
    var fontname = editdoc.queryCommandValue('FontName');
    if (fontname == null) { fontname_obj.value = null; }
    else {
      var found = 0;
      for (i=0; i<fontname_obj.length; i++) {
        if (fontname.toLowerCase() == fontname_obj[i].text.toLowerCase()) {
          fontname_obj.selectedIndex = i;
          found = 1;
        }
      }
      //if (found != 1) { fontname_obj.value = null; }     // for fonts not in list
    }
  }

  // Set FontSize pulldown
  var fontsize_obj = document.all["_" +objname+ "_FontSize"];
  if (fontsize_obj) {
    var fontsize = editdoc.queryCommandValue('FontSize');
    if (fontsize == null) { fontsize_obj.value = null; }
    else {
      var found = 0;
      for (i=0; i<fontsize_obj.length; i++) {
        if (fontsize == fontsize_obj[i].value) { fontsize_obj.selectedIndex = i; found=1; }
      }
      if (found != 1) { fontsize_obj.value = null; }     // for sizes not in list
    }
  }

  // Set FontStyle pulldown
  var classname_obj = document.all["_" +objname+ "_FontStyle"];
  if (classname_obj) {
    var curRange = editdoc.selection.createRange();

    // check element and element parents for class names
    var pElement;
    if (curRange.length) { pElement = curRange[0]; }              // control tange
    else                 { pElement = curRange.parentElement(); } // text range
    while (pElement && !pElement.className) { pElement = pElement.parentElement; }  // keep going up

    var thisClass = pElement ? pElement.className.toLowerCase() : "";
    if (!thisClass && classname_obj.value) { classname_obj.value = null; }
    else {
      var found = 0;
      for (i=0; i<classname_obj.length; i++) {
        if (thisClass == classname_obj[i].value.toLowerCase()) {
          classname_obj.selectedIndex = i;
          found=1;
        }
      }
      if (found != 1) { classname_obj.value = null; }     // for classes not in list
    }
  }

  // update button states
  var IDList = Array('Bold','Italic','Underline','StrikeThrough','SubScript','SuperScript','JustifyLeft','JustifyCenter','JustifyRight','InsertOrderedList','InsertUnorderedList');
  for (i=0; i<IDList.length; i++) {
    var btnObj = document.all["_" +objname+ "_" +IDList[i]];
    if (btnObj == null) { continue; }
    var cmdActive = editdoc.queryCommandState( IDList[i] );

    if (!cmdActive)  {                                  // option is OK
      if (btnObj.className != 'btn') { btnObj.className = 'btn'; }
      if (btnObj.disabled  != false) { btnObj.disabled = false; }
    } else if (cmdActive)  {                            // option already applied or mixed content
      if (btnObj.className != 'btnDown') { btnObj.className = 'btnDown'; }
      if (btnObj.disabled  != false)   { btnObj.disabled = false; }
    }
  }
}

/* ---------------------------------------------------------------------- *\
  Function    : editor_updateOutput
  Description : update hidden output field with data from wysiwg
\* ---------------------------------------------------------------------- */

function editor_updateOutput(objname) {
  var config     = document.all[objname].config;
  var editor_obj  = document.all["_" +objname+  "_editor"];       // html editor object
  var editEvent = editor_obj.contentWindow ? editor_obj.contentWindow.event : event;
  var isTextarea = (editor_obj.tagName.toLowerCase() == 'textarea');
  var editdoc = isTextarea ? null : editor_obj.contentWindow.document;

  // get contents of edit field
  var contents;
  if (isTextarea) { contents = editor_obj.value; }
  else            { contents = editdoc.body.innerHTML; }
  // check if contents has changed since the last time we ran this routine
  if (config.lastUpdateOutput && config.lastUpdateOutput == contents) { return; }
  else { config.lastUpdateOutput = contents; }

  // update hidden output field
  document.all[objname].value = contents;
  
}

/* ---------------------------------------------------------------------- *\
  Function    : editor_filterOutput
  Description :
\* ---------------------------------------------------------------------- */

function editor_filterOutput(objname) {
  editor_updateOutput(objname);
  var contents = document.all[objname].value;
  var config   = document.all[objname].config;

  // ignore blank contents
  if (contents.toLowerCase() == '<p>&nbsp;</p>') { contents = ""; }

  // filter tag - this code is run for each HTML tag matched
  var filterTag = function(tagBody,tagName,tagAttr) {
    tagName = tagName.toLowerCase();
    var closingTag = (tagBody.match(/^<\//)) ? true : false;

    // fix placeholder URLS - remove absolute paths that IE adds
    if (tagName == 'img') { tagBody = tagBody.replace(/(src\s*=\s*.)[^*]*(\*\*\*)/, "$1$2"); }
    if (tagName == 'a')   { tagBody = tagBody.replace(/(href\s*=\s*.)[^*]*(\*\*\*)/, "$1$2"); }

    // add additional tag filtering here

    // convert to vbCode
//    if      (tagName == 'b' || tagName == 'strong') {
//      if (closingTag) { tagBody = "[/b]"; } else { tagBody = "[b]"; }
//    }
//    else if (tagName == 'i' || tagName == 'em') {
//      if (closingTag) { tagBody = "[/i]"; } else { tagBody = "[i]"; }
//    }
//    else if (tagName == 'u') {
//      if (closingTag) { tagBody = "[/u]"; } else { tagBody = "[u]"; }
//    }
//    else {
//      tagBody = ""; // disallow all other tags!
//    }

    return tagBody;
  };

  // match tags and call filterTag
  RegExp.lastIndex = 0;
    var matchTag = /<\/?(\w+)((?:[^'">]*|'[^']*'|"[^"]*")*)>/g;   // this will match tags, but still doesn't handle container tags (textarea, comments, etc)

  contents = contents.replace(matchTag, filterTag);

  // remove nextlines from output (if requested)
  if (config.replaceNextlines) { 
    contents = contents.replace(/\r\n/g, ' ');
    contents = contents.replace(/\n/g, ' ');
    contents = contents.replace(/\r/g, ' ');
  }

  // update output with filtered content
  document.all[objname].value = contents;

}
function isValidLength(value){
  deubgger;
  if(value.length >= 100)
       return false;
      else return true;
    }

/* ---------------------------------------------------------------------- *\
  Function    : editor_setmode
  Description : change mode between WYSIWYG and HTML editor
  Usage       : editor_setmode(objname, mode);
  Arguments   : objname - button id string with editor and action name
                mode      - init, textedit, or wysiwyg
\* ---------------------------------------------------------------------- */

function editor_setmode(objname, mode) {
  var config     = document.all[objname].config;
  var editor_obj = document.all["_" +objname + "_editor"];

  // wait until document is fully loaded
  if (document.readyState != 'complete') {
    setTimeout(function() { editor_setmode(objname,mode) }, 25);
    return;
  }

  // define different editors
  var TextEdit   = '<textarea ID="_' +objname + '_editor" style="width:' +config.width+ '; height:' +config.height+ '; margin-top: -1px; margin-bottom: -1px;"></textarea>';

  if(config.inDesigner){
    var RichEdit   = '<iframe src="' + rewriteURL('/portal/blank.jsp') + '" ID="_' +objname+ '_editor" style="width:' +config.width+ '; height:' +config.height+ ';" onblur="getObject(\''+objname+'\').onchange();"></iframe>';}
  else{
    var RichEdit   = '<iframe src="' + rewriteURL('/portal/blank.jsp') + '" ID="_' +objname+ '_editor"  onblur="getObject(\''+objname+'\').onchange();"  style="width:' +config.width+ '; height:' +config.height+ ';""></iframe>';
  }

 // src="' +_editor_url+ 'popups/blank.html"

  //
  // Switch to TEXTEDIT mode
  //

  if (mode == "textedit" || editor_obj.tagName.toLowerCase() == 'iframe') {
    config.mode = "textedit";
    var editdoc = editor_obj.contentWindow.document;
    var contents = editdoc.body.createTextRange().htmlText;
    editor_obj.outerHTML = TextEdit;
    editor_obj = document.all["_" +objname + "_editor"];
    editor_obj.value = contents;
    editor_event(objname);

    editor_updateToolbar(objname, "disable");  // disable toolbar items

    // set event handlers
    editor_obj.onkeydown   = function() { editor_event(objname); }
    editor_obj.onkeypress  = function() { editor_event(objname); }
    editor_obj.onkeyup     = function() { editor_event(objname); }
    editor_obj.onmouseup   = function() { editor_event(objname); }
    editor_obj.ondrop      = function() { cleanup_pasted(objname); }
    editor_obj.oncut       = function() { editor_event(objname, 100); }
    editor_obj.onpaste     = function() { cleanup_pasted(objname);	}
    editor_obj.onblur      = function() { editor_event(objname, -1); }

    editor_updateOutput(objname);
    editor_focus(editor_obj);
  }

  //
  // Switch to WYSIWYG mode
  //

  else {
    config.mode = "wysiwyg";
    var contents = editor_obj.value;
    if (mode == 'init') { contents = document.all[objname].value; } // on init use original textarea content

    // create editor
    editor_obj.outerHTML = RichEdit;
    editor_obj = document.all["_" +objname + "_editor"];

    // get iframe document object

    // create editor contents (and default styles for editor)
    var html = "";
    html += '<html><head>\n';
    if (config.stylesheet) {
      html += '<link href="' +config.stylesheet+ '" rel="stylesheet" type="text/css">\n';
    }
    html += '<style>\n';
    html += 'body {' +config.bodyStyle+ '} \n';
    for (var i in config.fontstyles) {
if (typeof config.fontstyles[i] == 'function') continue;
      var fontstyle = config.fontstyles[i];
      if (fontstyle.classStyle) {
        html += '.' +fontstyle.className+ ' {' +fontstyle.classStyle+ '}\n';
      }
    }
    html += '</style>\n'
      + '</head>\n'
      + '<body contenteditable="true" topmargin=1 leftmargin=1'

// still working on this
//      + ' oncontextmenu="parent.editor_cMenu_generate(window,\'' +objname+ '\');"'
      +' onmouseup="parent.updateMiniRange(this)" onkeyup="parent.updateMiniRange(this)" >'
      + contents
      + '</body>\n'
      + '</html>\n';

    // write to editor window
    var editdoc = editor_obj.contentWindow.document;

    editdoc.open();
    editdoc.write(html);
    editdoc.close();

    editor_updateToolbar(objname, "enable");  // enable toolbar items

    // store objname under editdoc
    editdoc.objname = objname;

    // set event handlers
    editdoc.onkeydown      = function() { editor_event(objname); }
    editdoc.onkeypress     = function() { editor_event(objname); }
    editdoc.onkeyup        = function() { editor_event(objname); }
    editdoc.onmouseup      = function() { editor_event(objname); }
    editdoc.body.ondrop    = function() { editor_event(objname); }     // these events fire before they occur
    editdoc.body.oncut     = function() { editor_event(objname, 100); }
    editdoc.body.onpaste   = function() { cleanup_pasted(objname); }
    editdoc.body.onblur    = function() { editor_event(objname, -1); }

    // bring focus to editor
    if (mode != 'init') {             // don't focus on page load, only on mode switch
      editor_focus(editor_obj);
    }

  }

  // Call update UI
  if (mode != 'init') {             // don't update UI on page load, only on mode switch
    editor_event(objname);
  }

}

/* ---------------------------------------------------------------------- *\
  Function    : editor_focus
  Description : bring focus to the editor
  Usage       : editor_focus(editor_obj);
  Arguments   : editor_obj - editor object
\* ---------------------------------------------------------------------- */

function editor_focus(editor_obj) {
  // check editor mode
  if (editor_obj.tagName.toLowerCase() == 'textarea') {         // textarea
    var myfunc = function() { editor_obj.focus(); };
    setTimeout(myfunc,100);                                     // doesn't work all the time without delay
  }

  else {                                                        // wysiwyg
    var editbody = editor_obj.contentWindow.document.body;            // get iframe editor document object
    if(!editbody.range){
      moveCursor(editbody, editbody.innerHTML.length);
      updateMiniRange(editbody);
    } else editbody.range.select();
    var myfunc = function() { editbody.focus(); };
    setTimeout(myfunc,100);                                     // doesn't work all the time without delay
  }

}

/* ---------------------------------------------------------------------- *\
  Function    : editor_about
  Description : display "about this editor" popup
\* ---------------------------------------------------------------------- */

function editor_about(objname) {
  showModalDialog(_editor_url + "popups/about.html", window, "resizable: yes; help: no; status: no; scroll: no; ");
}

/* ---------------------------------------------------------------------- *\
  Function    : _dec_to_rgb
  Description : convert dec color value to rgb hex
  Usage       : var hex = _dec_to_rgb('65535');   // returns FFFF00
  Arguments   : value   - dec value
\* ---------------------------------------------------------------------- */

function _dec_to_rgb(value) {
  var hex_string = "";
  for (var hexpair = 0; hexpair < 3; hexpair++) {
    var myByte = value & 0xFF;            // get low byte
    value >>= 8;                        // drop low byte
    var nybble2 = myByte & 0x0F;          // get low nybble (4 bits)
    var nybble1 = (myByte >> 4) & 0x0F;   // get high nybble
    hex_string += nybble1.toString(16); // convert nybble to hex
    hex_string += nybble2.toString(16); // convert nybble to hex
  }
  return hex_string.toUpperCase();
}

/* ---------------------------------------------------------------------- *\
  Function    : editor_insertHTML
  Description : insert string at current cursor position in editor.  If
                two strings are specifed, surround selected text with them.
  Usage       : editor_insertHTML(objname, str1, [str2], reqSelection)
  Arguments   : objname - ID of textarea
                str1 - HTML or text to insert
                str2 - HTML or text to insert (optional argument)
                reqSelection - (1 or 0) give error if no text selected
\* ---------------------------------------------------------------------- */

function editor_insertHTML(objname, str1,str2, reqSel) {
  var config     = document.all[objname].config;
  var editor_obj = document.all["_" +objname + "_editor"];    // editor object
  if (str1 == null) { str1 = ''; }
  if (str2 == null) { str2 = ''; }

  // for non-wysiwyg capable browsers just add to end of textbox
  if (document.all[objname] && editor_obj == null) {
    document.all[objname].focus();
    document.all[objname].value = document.all[objname].value + str1 + str2;
    return;
  }

  // error checking
  if (editor_obj == null) { return alert("Unable to insert HTML.  Invalid object name '" +objname+ "'."); }

  editor_focus(editor_obj);

  var tagname = editor_obj.tagName.toLowerCase();
  var sRange;

 // insertHTML for wysiwyg iframe
  if (tagname == 'iframe') {
    var editdoc = editor_obj.contentWindow.document;
    sRange  = editdoc.selection.createRange();
    var sHtml   = sRange.htmlText;

    // check for control ranges
    if (sRange.length) { return alert("Unable to insert HTML.  Try highlighting content instead of selecting it."); }

    // insert HTML
    var oldHandler = window.onerror;
    window.onerror = function() { alert("Unable to insert HTML for current selection."); return true; } // partial table selections cause errors
    if (sHtml.length) {                                 // if content selected
      if (str2) { sRange.pasteHTML(str1 +sHtml+ str2) } // surround
      else      { sRange.pasteHTML(str1); }             // overwrite
    } else {                                            // if insertion point only
      if (reqSel) { return alert("Unable to insert HTML.  You must select something first."); }
      sRange.pasteHTML(str1 + str2);                    // insert strings
    }
    window.onerror = oldHandler;
  }

  // insertHTML for plaintext textarea
  else if (tagname == 'textarea') {
    editor_obj.focus();
    sRange  = document.selection.createRange();
    var sText   = sRange.text;

    // insert HTML
    if (sText.length) {                                 // if content selected
      if (str2) { sRange.text = str1 +sText+ str2; }  // surround
      else      { sRange.text = str1; }               // overwrite
    } else {                                            // if insertion point only
      if (reqSel) { return alert("Unable to insert HTML.  You must select something first."); }
      sRange.text = str1 + str2;                        // insert strings
    }
  }
  else { alert("Unable to insert HTML.  Unknown object tag type '" +tagname+ "'."); }

  // move to end of new content
  sRange.collapse(false); // move to end of range
  sRange.select();        // re-select

}
/* ---------------------------------------------------------------------- *\
  Function    : editor_getCursorSelection
  Description : return range of cursor selection in HTMLArea Content. Used 
                when cursor point needs to be stored before button action
                as seen in the expression editor button (custom9)
  Usage       : var range = editor_getCursorSelection('objname');
\* ---------------------------------------------------------------------- */

function editor_getCursorSelection(objname) {
  var config     = document.all[objname].config;
  var editor_obj = document.all["_" +objname + "_editor"];    // editor object

  // for non-wysiwyg capable browsers just add to end of textbox
  if (document.all[objname] && editor_obj == null) {
    document.all[objname].focus();
    document.all[objname].value = document.all[objname].value + str1 + str2;
    return;
  }

  // error checking
  if (editor_obj == null) { return alert("Unable to insert HTML.  Invalid object name '" +objname+ "'."); }

  editor_focus(editor_obj);

  var tagname = editor_obj.tagName.toLowerCase();
  var sRange;

 // insertHTML for wysiwyg iframe
  if (tagname == 'iframe') {
    var editdoc = editor_obj.contentWindow.document;
    sRange  = editdoc.selection.createRange();

    return sRange;
  }
   else { alert("Unable to insert HTML.  Unknown object tag type '" +tagname+ "'."); }

}
/* ---------------------------------------------------------------------- *\
  Function    : editor_insertHTMLatCursor(sRange, objname, str1, str2, reqSel)
                insert string at current cursor position in editor.  If
                two strings are specifed, surround selected text with them.
  Usage       : editor_insertHTML(sRange, objname, str1, [str2], reqSelection)
  Arguments   : sRange - point of cursor before button action, retrieved from editor_getCursorSelection()
                objname - ID of textarea
                str1 - HTML or text to insert
                str2 - HTML or text to insert (optional argument)
                reqSelection - (1 or 0) give error if no text selected
\* ---------------------------------------------------------------------- */

 function editor_insertHTMLatCursor(sRange, objname, str1,str2, reqSel) {
  var sHtml   = sRange.htmlText;
  if (str1 == null) { str1 = ''; }
  if (str2 == null) { str2 = ''; }

    // check for control ranges
    if (sRange.length) { return alert("Unable to insert HTML.  Try highlighting content instead of selecting it."); }

    // insert HTML
    var oldHandler = window.onerror;
    window.onerror = function() { alert("Unable to insert HTML for current selection."); return true; } // partial table selections cause errors
    if (sHtml.length) {                                 // if content selected
      if (str2) { sRange.pasteHTML(str1 +sHtml+ str2) } // surround
      else      { sRange.pasteHTML(str1); }             // overwrite
    } else {                                            // if insertion point only
      if (reqSel) { return alert("Unable to insert HTML.  You must select something first."); }
      sRange.pasteHTML(str1 + str2);                    // insert strings
    }
    window.onerror = oldHandler;
     // move to end of new content
    sRange.collapse(false); // move to end of range
     sRange.select();        // re-select
 }

/* ---------------------------------------------------------------------- *\
  Function    : editor_getHTML
  Description : return HTML contents of editor (in either wywisyg or html mode)
  Usage       : var myHTML = editor_getHTML('objname');
\* ---------------------------------------------------------------------- */

function editor_getHTML(objname) {
  var editor_obj = document.all["_" +objname + "_editor"];
  var isTextarea = (editor_obj.tagName.toLowerCase() == 'textarea');

  if (isTextarea) { return editor_obj.value; }
  else            { return editor_obj.contentWindow.document.body.innerHTML; }
}

/* ---------------------------------------------------------------------- *\
  Function    : editor_setHTML
  Description : set HTML contents of editor (in either wywisyg or html mode)
  Usage       : editor_setHTML('objname',"<b>html</b> <u>here</u>");
\* ---------------------------------------------------------------------- */

function editor_setHTML(objname, html) {
  var editor_obj = document.all["_" +objname + "_editor"];
  var isTextarea = (editor_obj.tagName.toLowerCase() == 'textarea');

  if (isTextarea) { editor_obj.value = html; }
  else            { editor_obj.contentWindow.document.body.innerHTML = html; }
}

/* ---------------------------------------------------------------------- *\
  Function    : editor_appendHTML
  Description : append HTML contents to editor (in either wywisyg or html mode)
  Usage       : editor_appendHTML('objname',"<b>html</b> <u>here</u>");
\* ---------------------------------------------------------------------- */

function editor_appendHTML(objname, html) {
  var editor_obj = document.all["_" +objname + "_editor"];
  var isTextarea = (editor_obj.tagName.toLowerCase() == 'textarea');

  if (isTextarea) { editor_obj.value += html; }
  else            { editor_obj.contentWindow.document.body.innerHTML += html; }
}

/* ---------------------------------------------------------------- */

function _isMouseOver(obj,event) {       // determine if mouse is over object
  var mouseX    = event.clientX;
  var mouseY    = event.clientY;

  var objTop    = obj.offsetTop;
  var objBottom = obj.offsetTop + obj.offsetHeight;
  var objLeft   = obj.offsetLeft;
  var objRight  = obj.offsetLeft + obj.offsetWidth;

  if (mouseX >= objLeft && mouseX <= objRight &&
      mouseY >= objTop  && mouseY <= objBottom) { return true; }

  return false;
}

/* ---------------------------------------------------------------- */

function editor_cMenu_generate(editorWin,objname) {
  var parentWin = window;
  editorWin.event.returnValue = false;  // cancel default context menu

  // define content menu options
  var cMenuOptions = [ // menu name, shortcut displayed, javascript code
    ['Cut', 'Ctrl-X', function() {}],
    ['Copy', 'Ctrl-C', function() {}],
    ['Paste', 'Ctrl-V', function() {}],
    ['Delete', 'DEL', function() {}],
    ['---', null, null],
    ['Select All', 'Ctrl-A', function() {}],
    ['Clear All', '', function() {}],
    ['---', null, null],
    ['About this editor...', '', function() {
      alert("about this editor");
    }]];
    editor_cMenu.options = cMenuOptions; // save options

  // generate context menu
  var cMenuHeader = ''
    + '<div id="_'+objname+'_cMenu" onblur="editor_cMenu(this);" oncontextmenu="return false;" onselectstart="return false"'
    + '  style="position: absolute; visibility: hidden; cursor: default; width: 167px; background-color: threedface;'
    + '         border: solid 1px; border-color: threedlightshadow threeddarkshadow threeddarkshadow threedlightshadow;">'
    + '<table border=0 cellspacing=0 cellpadding=0 width="100%" style="width: 167px; background-color: threedface; border: solid 1px; border-color: threedhighlight threedshadow threedshadow threedhighlight;">'
    + ' <tr><td colspan=2 height=1></td></tr>';

  var cMenuList = '';

  var cMenuFooter = ''
    + ' <tr><td colspan=2 height=1></td></tr>'
    + '</table></div>';

  for (var menuIdx in editor_cMenu.options) {
if (typeof editor_cMenu.options[menuIdx] == 'function') continue;
    var menuName = editor_cMenu.options[menuIdx][0];
    var menuKey  = editor_cMenu.options[menuIdx][1];
    var menuCode = editor_cMenu.options[menuIdx][2];

    // separator
    if (menuName == "---" || menuName == "separator") {
      cMenuList += ' <tr><td colspan=2 class="cMenuDivOuter"><div class="cMenuDivInner"></div></td></tr>';
    }

    // menu option
    else {
      cMenuList += '<tr class="cMenu" onMouseOver="editor_cMenu(this)" onMouseOut="editor_cMenu(this)" onClick="editor_cMenu(this, \'' +menuIdx+ '\',\'' +objname+ '\')">';
      if (menuKey) { cMenuList += ' <td align=left class="cMenu">' +menuName+ '</td><td align=right class="cMenu">' +menuKey+ '</td>'; }
      else         { cMenuList += ' <td colspan=2 class="cMenu">' +menuName+ '</td>'; }
      cMenuList += '</tr>';
    }
  }

  var cMenuHTML = cMenuHeader + cMenuList + cMenuFooter;


  document.all['_'+objname+'_cMenu'].outerHTML = cMenuHTML;

  editor_cMenu_setPosition(parentWin, editorWin, objname);

  parentWin['_'+objname+'_cMenu'].style.visibility = 'visible';
  parentWin['_'+objname+'_cMenu'].focus();

}

/* ---------------------------------------------------------------- */

function editor_cMenu_setPosition(parentWin, editorWin, objname) {      // set object position that won't overlap window edge
  var event    = editorWin.event;
  var cMenuObj = parentWin['_'+objname+'_cMenu'];
  var mouseX   = event.clientX + parentWin.document.all['_'+objname+'_editor'].offsetLeft;
  var mouseY   = event.clientY + parentWin.document.all['_'+objname+'_editor'].offsetTop;
  var cMenuH   = cMenuObj.offsetHeight;
  var cMenuW   = cMenuObj.offsetWidth;
  var pageH    = document.body.clientHeight + document.body.scrollTop;
  var pageW    = document.body.clientWidth + document.body.scrollLeft;

  // set horzontal position
  if (mouseX + 5 + cMenuW > pageW) { var left = mouseX - cMenuW - 5; } // too far right
  else                            { var left = mouseX + 5; }

  // set vertical position
  if (mouseY + 5 + cMenuH > pageH) { var top = mouseY - cMenuH + 5; } // too far down
  else                            { var top = mouseY + 5; }

  cMenuObj.style.top = top;
  cMenuObj.style.left = left;

}

/* ---------------------------------------------------------------- */

function editor_cMenu(obj,menuIdx,objname) {
  var action = event.type;
  if      (action == "mouseover" && !obj.disabled && obj.tagName.toLowerCase() == 'tr') {
    obj.className = 'cMenuOver';
    for (var i=0; i < obj.cells.length; i++) { obj.cells[i].className = 'cMenuOver'; }
  }
  else if (action == "mouseout" && !obj.disabled && obj.tagName.toLowerCase() == 'tr')  {
    obj.className = 'cMenu';
    for (var i=0; i < obj.cells.length; i++) { obj.cells[i].className = 'cMenu'; }
  }
  else if (action == "click" && !obj.disabled) {
    document.all['_'+objname+'_cMenu'].style.visibility = "hidden";
    var menucode = editor_cMenu.options[menuIdx][2];
    menucode();
  }
  else if (action == "blur") {
    if (!_isMouseOver(obj,event)) { obj.style.visibility = 'hidden'; }
    else {
      if (obj.style.visibility != "hidden") { obj.focus(); }
    }
  }
  else { alert("editor_cMenu, unknown action: " + action); }
}

function cleanup_pasted(objname) { 
	var mytext = window.clipboardData.getData('Text'); 
	window.clipboardData.clearData('Text'); 
	window.clipboardData.clearData('HTML'); 
	window.clipboardData.setData('Text', mytext); 
} 

function updateMiniRange(body){
  var sel = body.ownerDocument.selection;
  body.range = sel.createRange();
  body.focus();
}
/* ---------------------------------------------------------------------- */
/**
* Generates and returns the markup for an Editable Select element, which is a
* select element that you can type in.  If your display values and actual
* values don't match, use EditableSelectHTML2.
* @param name: The name of the form element.  Quite standard, really.
* @param value: The default value of the editable select element.
* @param opts: The dropdown options, given as a single array in the form:
* [[name1, value1], [name2, value2]] etc.
* A fourth parameter (optional) can be used to append events to the text field.
* @return HTML for an Editable Select element
*/
function EditableSelectHTML(name, value, opts) {
  value = value || 'Type value here';
  var out = "<div class=\"editableSelect\"><input name=\"" + name + "\" ";
  if (arguments[3]) out+= arguments[3];
  out += " value=\"" + value + "\" onclick=\"if (this.value == 'Type value here') this.value = '';\" /><div>";
  out += "<iframe scrolling=\"no\" frameBorder=\"1\" style=\"filter: alpha(opacity=0)\" src=\"" + rewriteURL('/portal/blank.jsp') + "\"></iframe></div><select"
  out += " onchange=\"this.parentNode.childNodes[0].value = (this.value == \'null\') ? \'\' : this.value;";
  out += " this.parentNode.childNodes[0].focus();try{this.parentNode.childNodes[0].onchange()}catch(e){}\">";
  out += "<option value=\"null\"></option>";
  for (var i = 0; i < opts.length; i++) {
    out += "<option value=\"" + opts[i][1] + "\" ";
    out += value == opts[i][1] ? 'selected=\"true\" ' : '';
    out +=">" + opts[i][0] + "</option>";
  }
  out += "</select></div>";
  return out;
}
/**
* Same as above, except this can keep track of differences between options'
* display values and actual values.  Bear in mind that if the user types in the
* display value of one of the dropdown options, the actual value of that
* dropdown option will get submitted.
* @param name: The name of the form element.  Quite standard, really.
* @param value: The default value of the editable select element.
* @param opts: The dropdown options, given as a single array in the form:
* [[name1, value1], [name2, value2]] etc.
* @param customEvent: Optional script to append to the onblur event of the
* text field.  Can be hacked with to specify other events.  For example,
* '" onchange="blah()' closes the onblur event and starts an onchange.
* @return HTML for an Editable Select2 element
*/
function EditableSelectHTML2(name, value, opts, customEvent) {
  var optsHTML = '';
  opts.push(['Type in a value...', '']);
  var displayValue = value;

  for (var i = 0; i < opts.length; i++) {
    optsHTML += '<option value="' + opts[i][1] + '" ';
    optsHTML += value == opts[i][1] ? 'selected="true" ' : '';
    optsHTML +='>' + opts[i][0] + '</option>';
    if(value == opts[i][1]) displayValue = opts[i][0];
  }

  var out = '<div class="editableSelect"><input onblur="updateESValue(this);';
    if (customEvent) out+= customEvent;
    out += '" value="' + displayValue + '" onclick="if (this.value == \'Type in a value...\') this.value = \'\';" />';
    out += '<div><iframe scrolling="no" frameBorder="1" style="filter: alpha(opacity=0)" src="' + rewriteURL('/portal/blank.jsp') + '"></iframe></div>';
    out += '<select onchange="EditableSelectOnChoose(this)">';
    out += optsHTML;
    out += '</select>';
  out += '<input type="hidden" name="' + name + '" value="' + value + '" />';
  out +='</div>';
  return out;
}
function EditableSelectOnChoose(es){
  var displayValue = es.options[es.selectedIndex].innerHTML;
  es.parentNode.childNodes[0].value = (displayValue == 'Type in a value...') ? '' : displayValue;
  getInputsByType('hidden', es.parentNode)[0].value = es.value;
  es.parentNode.childNodes[0].focus();
  es.value = es.value + 'fireIt';  /* This makes the select's onchange fire even when reselecting 'Type in a value'.  Ask Max for details. */
  try{ this.parentNode.childNodes[0].onchange() } catch(e){};
}
function updateESValue(displayInput){
  var selectOptions = displayInput.parentNode.getElementsByTagName("SELECT")[0].options;
  var hiddenInput = getInputsByType('hidden', displayInput.parentNode)[0];
  for(var i=0; i<selectOptions.length; i++){
    if(displayInput.value == selectOptions[i].innerHTML){
      hiddenInput.value = selectOptions[i].value;
      return true;
    }
  }
  hiddenInput.value = displayInput.value;
}