/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 * 
 *  http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */

var _byteLenKey = "org.apache.myfaces.trinidad.validator.ByteLengthValidator.MAXIMUM";

function TrByteLengthValidator(
  length,
  messages
  )
{
  this._length   = length;
  this._messages = messages;
  this._class    = "TrByteLengthValidator";
}

TrByteLengthValidator.prototype = new TrValidator();

function CjkFormat(
  length,
  messages
  )
{
  this._base = TrByteLengthValidator;
  this._base(length, messages);
  this._class = "CjkFormat";
  
}

CjkFormat.prototype = new TrByteLengthValidator();
CjkFormat.prototype.getHints = function(
  converter
  )
{
  var messages = null;
  
  if(this._messages["hint"])
  {
  	messages = new Array();
    messages.push(TrMessageFactory.createCustomMessage(
      this._messages["hint"],
	    this._length)
	  );
  }
	return messages;
}
CjkFormat.prototype.validate  = function(
  parseString,
  label,
  converter
  )
{
  var i = 0;
  var length = this._length;

  while (i < parseString.length)
  { 
    var ch = parseString.charCodeAt(i);
    if ((ch < 0x80) || ((0xFF60 < ch) && (ch < 0xFFA0))) length--; 
    else length -= 2;
   
    if (length < 0)
    {
      var facesMessage;
      if(!this._messages["detail"])
      {
        facesMessage = _createFacesMessage(_byteLenKey,
                                           label,
                                           parseString,
                                           this._length);
      }
      else
      {
        facesMessage = _createCustomFacesMessage(
                                           TrMessageFactory.getSummaryString(_byteLenKey),
                                           this._messages["detail"],
                                           label,
                                           parseString,
                                           this._length);
      }
      throw new TrValidatorException(facesMessage);     
    }

    i++;
  }

  return parseString;
}




function Utf8Format(
  length,
  messages
  )
{
  this._base = TrByteLengthValidator;
  this._base(length, messages);
  this._class = "Utf8Format";
}


Utf8Format.prototype = new TrByteLengthValidator();
Utf8Format.prototype.getHints = function(
  converter
  )
{
  var messages = null;
  
  if(this._messages["hint"])
  {
  	messages = new Array();
    messages.push(TrMessageFactory.createCustomMessage(
      this._messages["hint"],
	    this._length)
	  );
  }
	return messages;
}
Utf8Format.prototype.validate  = function(
  parseString,
  label,
  converter
  )
{
  var i = 0;
  var length = this._length;

  while (i < parseString.length)
  { 
    var ch = parseString.charCodeAt(i);
    if (ch < 0x80) length--;
    else if (ch < 0x800) length -= 2;
    else
    {
      // Surrogates;  see bug 3849516
      if ((ch & 0xF800) == 0xD800)
        length -= 2;
      else
        length -= 3;
    }

    if (length < 0)
    {
      var facesMessage;
      if(!this._messages["detail"])
      {
        facesMessage = _createFacesMessage(_byteLenKey,
                                           label,
                                           parseString,
                                           this._length);
      }
      else
      {
        facesMessage = _createCustomFacesMessage(
                                            TrMessageFactory.getSummaryString(_byteLenKey),
                                            this._messages["detail"],
                                            label,
                                            parseString,
                                            this._length);
      }
      throw new TrValidatorException(facesMessage);              
    }

    i++;
  }

  return parseString;
}

function SBFormat(
  length,
  messages
  )
{
  this._base = TrByteLengthValidator;
  this._base(length, messages);
  this._class = "SBFormat";
  
}


SBFormat.prototype = new TrByteLengthValidator();
SBFormat.prototype.getHints = function(
  converter
  )
{
  var messages = null;
  
  if(this._messages["hint"])
  {
  	messages = new Array();
    messages.push(TrMessageFactory.createCustomMessage(
      this._messages["hint"],
	    this._length)
	  );
  }
	return messages;
}
SBFormat.prototype.validate  = function(
  parseString,
  label,
  converter
  )
{
  if (this._length < parseString.length)
  {
      var facesMessage;
      if(!this._messages["detail"])
      {
        facesMessage = _createFacesMessage(_byteLenKey,
                                           label,
                                           parseString,
                                           this._length);
      }
      else
      {
        facesMessage = _createCustomFacesMessage(
                                            TrMessageFactory.getSummaryString(_byteLenKey),
                                            this._messages["detail"],
                                            label,
                                            parseString,
                                            this._length);
      }
    throw new TrValidatorException(facesMessage);      
  }

  return parseString;
}
/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 * 
 *  http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */

/**
 * constructor for TrNumberFormat.
 */
 function TrNumberFormat(type, locale)
{
  if(!type)
    alert("type for TrNumberFormat not defined!");
  this._type = type;
  
  this._localeSymbols = getLocaleSymbols(locale);
  this._pPre = this._localeSymbols.getPositivePrefix();
  this._pSuf = this._localeSymbols.getPositiveSuffix();
  this._nPre = this._localeSymbols.getNegativePrefix();
  this._nSuf = this._localeSymbols.getNegativeSuffix();

  //default values, similar to JDK (values from Apache Harmony)
  this._maxFractionDigits = 3;
  this._maxIntegerDigits  = 40;
  this._minFractionDigits = 0;
  this._minIntegerDigits  = 1;
  this._groupingUsed = true;
  
}
//***********************
// static
//***********************

/**
 * Returns a number formater.
 */
TrNumberFormat.getNumberInstance = function(locale)
{
  return new TrNumberFormat("number", locale);
}

/**
 * Returns a currency formater.
 */
TrNumberFormat.getCurrencyInstance = function(locale)
{
  return new TrNumberFormat("currency", locale);
}

/**
 * Returns a percent formater.
 */
TrNumberFormat.getPercentInstance = function(locale)
{
  return new TrNumberFormat("percent", locale);
}

/**
 * Sets whether this NumberFormat formats and parses numbers using a
 * grouping separator.
 * 
 * @param value true when a grouping separator is used, false otherwise
 */
TrNumberFormat.prototype.setGroupingUsed = function(groupingUsed)
{
  this._groupingUsed = groupingUsed;
}

/**
 * Answers whether this NumberFormat formats and parses numbers using a
 * grouping separator.
 * 
 * @return true when a grouping separator is used, false otherwise
 */
TrNumberFormat.prototype.isGroupingUsed = function()
{
  return this._groupingUsed;
}

/**
 * Used to specify the new maximum count of integer digits that are printed
 * when formatting. If the maximum is less than the number of integer
 * digits, the most significant digits are truncated.
 * 
 * @param value the new maximum number of integer numerals for display
 */
TrNumberFormat.prototype.setMaximumIntegerDigits = function(number)
{
  //taken from the Apache Harmony project
  if(number)
  {
    this._maxIntegerDigits = number < 0 ? 0 : number;
    if (this._minIntegerDigits > this._maxIntegerDigits)
    {
      this._minIntegerDigits = this._maxIntegerDigits;
    }
  }
} 

/**
 * Answers the maximum number of integer digits that are printed when
 * formatting. If the maximum is less than the number of integer digits, the
 * most significant digits are truncated.
 * 
 * @return the maximum number of integer digits
 */
TrNumberFormat.prototype.getMaximumIntegerDigits = function()
{
  //taken from the Apache Harmony project
  return this._maxIntegerDigits;
}

/**
 * Sets the maximum number of fraction digits that are printed when
 * formatting. If the maximum is less than the number of fraction digits,
 * the least significant digits are truncated.
 * 
 * @param value the maximum number of fraction digits
 */
TrNumberFormat.prototype.setMaximumFractionDigits = function(number)
{
  //taken from the Apache Harmony project
  if(number)
  {
    this._maxFractionDigits = number < 0 ? 0 : number;
    if (this._maxFractionDigits < this._minFractionDigits)
    {
      this._minFractionDigits = this._maxFractionDigits;
    }
  }
} 

/**
 * Answers the maximum number of fraction digits that are printed when
 * formatting. If the maximum is less than the number of fraction digits,
 * the least significant digits are truncated.
 * 
 * @return the maximum number of fraction digits
 */
TrNumberFormat.prototype.getMaximumFractionDigits = function()
{
  //taken from the Apache Harmony project
  return this._maxFractionDigits;
}

/**
 * Sets the minimum number of integer digits that are printed when
 * formatting.
 * 
 * @param value the minimum number of integer digits
 */
TrNumberFormat.prototype.setMinimumIntegerDigits = function(number)
{
  //taken from the Apache Harmony project
  if(number)
  {
    this._minIntegerDigits = number < 0 ? 0 : number;
    if(this._minIntegerDigits > this._maxIntegerDigits)
    {
      this._maxIntegerDigits = this._minIntegerDigits;
    }
  }
}

/**
 * Answers the minimum number of integer digits that are printed when
 * formatting.
 * 
 * @return the minimum number of integer digits
 */
TrNumberFormat.prototype.getMinimumIntegerDigits = function()
{
  //taken from the Apache Harmony project
  return this._minIntegerDigits;
}

/**
 * Sets the minimum number of fraction digits that are printed when
 * formatting.
 * 
 * @param value the minimum number of fraction digits
 */
TrNumberFormat.prototype.setMinimumFractionDigits = function(number)
{
  //taken from the Apache Harmony project
  if(number)
  {
    this._minFractionDigits = number < 0 ? 0 : number;
    if (this._maxFractionDigits < this._minFractionDigits)
    {
      this._maxFractionDigits = this._minFractionDigits;
    }
  }
}

/**
 * Answers the minimum number of fraction digits that are printed when
 * formatting.
 * 
 * @return the minimum number of fraction digits
 */
TrNumberFormat.prototype.getMinimumFractionDigits = function()
{
  //taken from the Apache Harmony project
  return this._minFractionDigits;
}

/**
 * Based on the type this func returns a percentage, currency or number string.
 */
TrNumberFormat.prototype.format = function(number)
{
  if(this._type=="percent")
    return this.percentageToString(number);
  else if (this._type=="currency")
    return this.currencyToString(number);
  else if (this._type=="number")
    return this.numberToString(number);
}

/**
 * Based on the type this func returns a number result, from the given formatted string.
 */
TrNumberFormat.prototype.parse = function(string)
{
  if(this._type=="percent")
    return this.stringToPercentage(string);
  else if (this._type=="currency")
    return this.stringToCurrency(string);
    
  // ELSE: assume this._type=="number"
  return this.stringToNumber(string);
}

/**
 * Formats a number string into a number.
 */
TrNumberFormat.prototype.stringToNumber = function(numberString)
{
  // parseFloat("123abc45") returns 123, but 123abc45 is considered an invalid number on the server, 
  // so check for a valid number first. Exclude non-numbers and disallow exponential notation.
  if (isNaN(numberString) || numberString.indexOf('e') != -1 || numberString.indexOf('E') != -1)
  {
    throw new TrParseException("not able to parse number");
  }
  return parseFloat(numberString);
}

/**
 * Formats a currency string into a number.
 */
TrNumberFormat.prototype.stringToCurrency = function(numberString)
{
  //is the string negative ?
  var negP = numberString.indexOf(this._nPre);
  
  if(negP != -1)
  {
    numberString = numberString.substr(this._nPre.length, numberString.length);
    var nSufNoSpace = this._nSuf;
    if (nSufNoSpace.charAt(0) == ' ' || nSufNoSpace.charAt(0) == '\xa0')
      nSufNoSpace = nSufNoSpace.substring(1);
    var negS = numberString.indexOf(nSufNoSpace);
    if(negS != -1)
    {
      numberString = numberString.substr(0, numberString.length - nSufNoSpace.length);
      return (this.stringToNumber(numberString) * -1);
    }
    else
    {
      throw new TrParseException("not able to parse number");
    }
  }
  else
  {
    var posP = numberString.indexOf(this._pPre);
    if(posP != -1)
    {
      numberString = numberString.substr(this._pPre.length, numberString.length);
      var pSufNoSpace = this._pSuf;
      if (pSufNoSpace.charAt(0) == ' ' || pSufNoSpace.charAt(0) == '\xa0')
        pSufNoSpace = pSufNoSpace.substring(1);
      var posS = numberString.indexOf(pSufNoSpace);
      if(posS != -1)
      {
        numberString = numberString.substr(0, numberString.length - pSufNoSpace.length);
        numberString = this.stringToNumber(numberString);
      }
      else
      {
        throw new TrParseException("not able to parse number");
      }
      return numberString;
    }
    else
    {
      throw new TrParseException("not able to parse number");
    }
  }
}

/**
 * Formats a percent string into a number.
 */
TrNumberFormat.prototype.stringToPercentage = function(percentString)
{
  var isPercentage = (percentString.indexOf('%') != -1);
  if (!isPercentage)
  {
    throw new TrParseException("not able to parse number");
  }
  
  var numberString = percentString.replace(/\%/g, '');
  return this.stringToNumber(numberString);
}

/**
 * Formats a number into a a formatted string.
 */
TrNumberFormat.prototype.numberToString = function(number)
{
  //negative ?
  var negative = number<0;
  if(negative)
    number = (number*-1);

  var numberString = number + "";
  var index = numberString.indexOf(".");
  var numberStringLength = numberString.length;
  var ints;
  var fracs;
  if(index != -1)
  {
    ints = numberString.substring(0, index);
    fracs = numberString.substring(index+1, numberStringLength);
  }
  else
  {
    ints = numberString;
    fracs = "";
  }

  ints  = this._formatIntegers(ints);
  fracs = this._formatFractions(fracs)
  
  var decimalSeparator = this._localeSymbols.getDecimalSeparator();

  if(fracs!="")
    numberString = (ints+decimalSeparator+fracs);
  else
    numberString = (ints);
  
  if(negative)
    numberString = "-" + numberString;
  
  return numberString;
  
}

/**
 * Formats a number into a a formatted currency string.
 */
TrNumberFormat.prototype.currencyToString = function(number)
{
  //negative ?
  if(number<0)
  {
    number = (number*-1)+"";
    number = this.numberToString(number);
    return this._nPre + number + this._nSuf;
  }
  else
  {
    number = this.numberToString(number);
    return this._pPre + number + this._pSuf;
  }
}

/**
 * Formats a number into a a formatted percent string.
 */
TrNumberFormat.prototype.percentageToString = function(number)
{
  number = number * 100;
  number = this.getRounded(number);
  if (isNaN(number))
  {
    throw new TrParseException("not able to parse number");
  } 
  
  // Get the percent suffix. I.e. in French the suffix is " %", not just "%". The following assumes 
  // the percent suffix is the same when the number is positive or negative. It also assumes that 
  // the locale indeed uses a percent suffix (and not a percent prefix); if that assumption is 
  // wrong, we should be notified by the following exception. If any changes need to be made, you 
  // should start by looking at _getPercentData() in:
  // maven-i18n-plugin\src\main\java\org\apache\myfaces\trinidadbuild\plugin\i18n\uixtools\JSLocaleElementsGenerator.java
  var suffix = this._localeSymbols.getPercentSuffix();
  if (!suffix || suffix == "")
  {
    throw new TrParseException("percent suffix undefined or empty");
  }
  
  number = this.numberToString(number);
  return number + suffix;
}

/**
 * helper for rounding values
 */
TrNumberFormat.prototype.getRounded = function(val)
{
  val = this.moveDecimalRight(val);
  val = Math.round(val);
  val = this.moveDecimalLeft(val);
  return val;
}

/**
 * helper for rounding values
 */
TrNumberFormat.prototype.moveDecimalRight = function(val)
{
  var newVal = '';
  newVal = this.moveDecimal(val, false);
  return newVal;
}

/**
 * helper for rounding values
 */
TrNumberFormat.prototype.moveDecimalLeft = function (val)
{
  var newVal = '';
  newVal = this.moveDecimal(val, true);
  return newVal;
}

/**
 * helper for rounding values
 */
TrNumberFormat.prototype.moveDecimal = function(val, left)
{
  var newVal = '';
  newVal = this.moveDecimalAsString(val, left);
  return parseFloat(newVal);
}

/**
 * helper for rounding values
 */
TrNumberFormat.prototype.moveDecimalAsString = function(val, left)
{
  //TODO: matzew make it nicer....
  var spaces = 2;
  if (spaces <= 0)
    return val; 
  var newVal = val + '';
  var extraZ = this.getZeros(spaces);
  var re1 = new RegExp('([0-9.]+)');
  if (left)
  {
    newVal = newVal.replace(re1, extraZ + '$1');
    var re2 = new RegExp('(-?)([0-9]*)([0-9]{' + spaces + '})(\\.?)');
    newVal = newVal.replace(re2, '$1$2.$3');
  }
  else
  {
    var reArray = re1.exec(newVal); 
    if (reArray != null)
    {
      newVal = newVal.substring(0,reArray.index) + reArray[1] + extraZ + newVal.substring(reArray.index + reArray[0].length); 
    }
    var re2 = new RegExp('(-?)([0-9]*)(\\.?)([0-9]{' + spaces + '})');
    newVal = newVal.replace(re2, '$1$2$4.');
  }
  newVal = newVal.replace(/\.$/, ''); 
  return newVal;
}

/**
 * 
 */
TrNumberFormat.prototype.getZeros = function(places)
{
  var extraZ = '';
  var i;
  for (i=0; i<places; i++) {
    extraZ += '0';
  }
  return extraZ;
}

//***********************
// PRIVATE
//***********************

/**
 * Formats the integer part of a number
 */
TrNumberFormat.prototype._formatIntegers = function(ints)
{
  var intsLength = ints.length;
  var maxInt = this.getMaximumIntegerDigits();
  var minInt = this.getMinimumIntegerDigits();

  var gap;
  if(intsLength>maxInt)
  {
    gap = intsLength-maxInt;
    ints = ints.substring(gap, intsLength);
  }
  else if(intsLength<minInt)
  {
    gap = minInt-intsLength;
    var leadingZeros = "";
    
    //we need some more leadingZeros
    while(gap>0)
    {
      leadingZeros = "0"+leadingZeros;
      --gap;
    }
    
    ints = leadingZeros + ints;
  }
  
  if(this.isGroupingUsed())
  {
    ints = this._addGroupingSeparators(ints);
  }

  return ints;
}

/**
 * Formats the fraction part of a number
 */
TrNumberFormat.prototype._formatFractions = function(fracs)
{
  var fracsLength = fracs.length;
  var maxFra = this.getMaximumFractionDigits();
  var minFra = this.getMinimumFractionDigits();

  if(fracsLength > maxFra && maxFra>minFra)
  {
    fracs = fracs.substring(0, maxFra);
  }
  if(fracsLength <minFra)
  {
    var gap = minFra-fracsLength;
    
    //we need to add some zeros
    while(gap>0)
    {
      fracs = fracs + "0";
      --gap;
    }
  }
  return fracs;
}

/**
 * Adds localized grouping separators to a number string.
 */
TrNumberFormat.prototype._addGroupingSeparators = function(ints)
{
  var counter = ints.length;
  var toMuch = counter%3;
  var balance;
  var toFormat;
  var formatted = "";
  var groupingSeparator = this._localeSymbols.getGroupingSeparator();

  if(toMuch>0)
  {
    balance = (counter < 4) ? ints.substring(0, toMuch) : ints.substring(0, toMuch) + groupingSeparator;
    toFormat = ints.substring(toMuch, counter);
  }
  else
  {
    balance = "";
    toFormat = ints;
  }

  for(i=0; i < toFormat.length; i++)
  {
    if(i%3==0 && i!=0)
    {
      formatted += groupingSeparator;
    }
    formatted += toFormat.charAt(i);
  }
  ints = balance + formatted;
  return ints;
}
/** 
 * TrParseException is an exception thrown by the TrNumberFormater.
 * TODO: loclized messages ?
 */
function TrParseException(
  message
  )
{
  this._message = message;
}
TrParseException.prototype.getMessage = function()
{
  return this._message;
}

/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 * 
 *  http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */

/**
 * constructor of client side NumberConverter class
 */ 
function TrNumberConverter(
  pattern,  
  type,
  locale,
  messages,
  integerOnly,
  groupingUsed,
  currencyCode,
  currencySymbol,
  maxFractionDigits,
  maxIntegerDigits,
  minFractionDigits,
  minIntegerDigits)
{
  this._pattern = pattern;
  this._type = type;
  this._locale = locale;
  this._messages = messages;
  this._currencyCode = currencyCode;
  this._currencySymbol = currencySymbol;
  this._maxFractionDigits = maxFractionDigits;
  this._maxIntegerDigits = maxIntegerDigits;
  this._minFractionDigits = minFractionDigits;
  this._minIntegerDigits = minIntegerDigits;
  
  //set the integerOnly value
  if(integerOnly !== undefined)
    this._integerOnly = integerOnly;
  else
    this._integerOnly = false;
    
  //set the groupingUsed value
  if(groupingUsed !== undefined)
    this._groupingUsed = groupingUsed;
  else
    this._groupingUsed = true;
    
  //init the TrNumberFormat
  this._initNumberFormat(locale);
  
  // for debugging
  this._class = "TrNumberConverter";

}

TrNumberConverter.prototype = new TrConverter();

//***********************
// PUBLIC
//***********************

TrNumberConverter.prototype.setCurrencyCode = function(currencyCode)
{
  this._currencyCode = currencyCode;
}
TrNumberConverter.prototype.getCurrencyCode = function()
{
  return this._currencyCode;
}
TrNumberConverter.prototype.setCurrencySymbol = function(currencySymbol)
{
  this._currencySymbol = currencySymbol;
}
TrNumberConverter.prototype.getCurrencySymbol = function()
{
  return this._currencySymbol;
}

TrNumberConverter.prototype.setMaxFractionDigits = function(maxFractionDigits)
{
  this._maxFractionDigits = maxFractionDigits;
}
TrNumberConverter.prototype.getMaxFractionDigits = function()
{
  return this._maxFractionDigits;
}

TrNumberConverter.prototype.setMaxIntegerDigits = function(maxIntegerDigits)
{
  this._maxIntegerDigits = maxIntegerDigits;
}
TrNumberConverter.prototype.getMaxIntegerDigits = function()
{
  return this._maxIntegerDigits ;
}

TrNumberConverter.prototype.setMinFractionDigits = function(minFractionDigits)
{
  this._minFractionDigits = minFractionDigits;
}
TrNumberConverter.prototype.getMinFractionDigits = function()
{
  return this._minFractionDigits;
}

TrNumberConverter.prototype.setMinIntegerDigits = function(minIntegerDigits)
{
  this._minIntegerDigits = minIntegerDigits;
}
TrNumberConverter.prototype.getMinIntegerDigits = function()
{
  return this._minIntegerDigits;
}

TrNumberConverter.prototype.setGroupingUsed = function(groupingUsed)
{
  this._groupingUsed = groupingUsed;
}
TrNumberConverter.prototype.isGroupingUsed = function()
{
  return this._groupingUsed;
}

TrNumberConverter.prototype.setIntegerOnly = function(integerOnly)
{
  this._integerOnly = integerOnly;
}
TrNumberConverter.prototype.isIntegerOnly = function()
{
  return this._integerOnly;
}

TrNumberConverter.prototype.getFormatHint = function()
{
  if(this._messages && this._messages["hintPattern"])
  {
    return TrMessageFactory.createCustomMessage(
      this._messages["hintPattern"],
      this._pattern);
  }
  else
  {
    if(this._pattern)
    {
      return TrMessageFactory.createMessage(
      "org.apache.myfaces.trinidad.convert.NumberConverter.FORMAT_HINT",
      this._pattern);
    }
    else
    {
      return null;
    }
  }
}

/**
 * Returns the number value as string or undefined (see also _isConvertible).
 */
TrNumberConverter.prototype.getAsString = function(
  number,
  label
  )
{
  if(this._isConvertible())
  {
    if(this._type=="percent" || this._type=="currency")
    {
      var string = this._numberFormat.format(number);
      if(this._type=="currency")
      {
        //In Trinidad the currencyCode gets preference over currencySymbol
        //this is similar on the server-side
        if(this._currencyCode)
        {
          string = string.replace(getLocaleSymbols().getCurrencyCode(), this._currencyCode);
        }
        else if(this._currencySymbol)
        {
          string = string.replace(getLocaleSymbols().getCurrencySymbol(), this._currencySymbol);
        }
      }
      return string;
    }
    else
    {
      if(typeof number === "string")
      {
        return this._numberFormat.format(parseFloat(number));
      }
      else
      {
        return this._numberFormat.format(parseFloat(number.toFixed(this._numberFormat.getMaximumFractionDigits())));
      }
    }
  }
  else
  {
    return undefined;
  }
}

/**
 * Returns the number value for the submitted string or undefined (see also _isConvertible).
 */
TrNumberConverter.prototype.getAsObject = function(
  numberString,
  label
  )
{
  if(this._isConvertible())
  {
    // The following are from the javadoc for Number and DateTimeConverter.
    // If the specified String is null, return a null. Otherwise, trim leading and trailing whitespace before proceeding.
    // If the specified String - after trimming - has a zero length, return null.
    if (numberString == null)
      return null;
    
    numberString = TrUIUtils.trim(numberString);
    if (numberString.length == 0)
      return null

    var parsedValue;
    if(this._type=="percent" || this._type=="currency")
    {
      var localeSymbols = getLocaleSymbols(this._locale);
      
      // TODO matzew - see TRINIDAD-682
      // Remove the thousands separator - which Javascript doesn't want to see
      var groupingSeparator = localeSymbols.getGroupingSeparator();
      
      if (groupingSeparator == "\xa0")
      {
        var normalSpace = new RegExp("\\ " , "g");
        numberString = numberString.replace(normalSpace, "\xa0");
      }
      
      var grouping = new RegExp("\\" + groupingSeparator, "g");
      numberString = numberString.replace(grouping, "");

      // Then change the decimal separator into a period, the only
      // decimal separator allowed by JS
      var decimalSeparator = localeSymbols.getDecimalSeparator();
      var decimal = new RegExp("\\" + decimalSeparator, "g");
      numberString = numberString.replace(decimal, ".");
      
      try
      {
        // parse the numberString
        numberString = this._numberFormat.parse(numberString)+"";     
      }
      catch(e)
      {
        // The user could have just left off the percent/currency symbol, so try 
        // parsing 'numberString' as a Number instead; if it still fails, then 
        // throw a converter exception.
        try
        {
          numberString = TrNumberFormat.getNumberInstance().parse(numberString)+"";
        }
        catch (e)
        {
          var facesMessage;
          var example = this._numberFormat.format(this._example);
          var key = "org.apache.myfaces.trinidad.convert.NumberConverter.CONVERT_" + this._type.toUpperCase();
          if (this._messages && this._messages[this._type])
          {
            facesMessage = _createCustomFacesMessage(TrMessageFactory.getSummaryString(key), this._messages[this._type], label, numberString, example);
          }
          else 
          {
            facesMessage = _createFacesMessage(key, label, numberString, example);
          }

          throw new TrConverterException(facesMessage);
        }
      }
      
      // to be able to pass the _decimalParse, we replace the decimal separator...
      // Note that _decimalParse uses the page locale.
      var jsSeparator = new RegExp("\\" + ".",  "g");
      numberString = numberString.replace(jsSeparator, getLocaleSymbols().getDecimalSeparator());
    }
    
    parsedValue = _decimalParse(numberString, 
                         this._messages,
                         "org.apache.myfaces.trinidad.convert.NumberConverter",
                         null,
                         null,
                         null,
                         null,
                         label,
                         !this.isIntegerOnly());

    parsedValue = parseFloat(parsedValue.toFixed(this._numberFormat.getMaximumFractionDigits()));

    if(this._type=="percent")
    {
      parsedValue = parsedValue / 100;
    }
    return parsedValue;
  }
  else
  {
    return undefined;
  }
}

//***********************
// PRIVATE
//***********************

/**
 * Checks if this converter can convert the value, which
 * is only true, if no pattern is set and the type is a number
 */
TrNumberConverter.prototype._isConvertible = function()
{
  // The locale attribute is now supported on convertNumber.
  return (this._pattern == null);
}

/**
 * runs the creation of the used TrNumberFormat class
 */
TrNumberConverter.prototype._initNumberFormat = function(locale)
{
  if(this._type=="percent")
  {
    this._example = 0.3423;
    this._numberFormat = TrNumberFormat.getPercentInstance(locale);
  }
  else if(this._type=="currency")
  {
    this._example = 10250;
    this._numberFormat = TrNumberFormat.getCurrencyInstance(locale);
  }
  else if(this._type=="number")
  {
  	this._numberFormat = TrNumberFormat.getNumberInstance(locale);
  }

  this._numberFormat.setGroupingUsed(this.isGroupingUsed());
  this._numberFormat.setMaximumFractionDigits(this.getMaxFractionDigits());
  this._numberFormat.setMaximumIntegerDigits(this.getMaxIntegerDigits());
  this._numberFormat.setMinimumFractionDigits(this.getMinFractionDigits());
  this._numberFormat.setMinimumIntegerDigits(this.getMinIntegerDigits());
}

/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 * 
 *  http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */

/**
 * Utilities for working with collections
 */
var TrCollections = new Object();

/**
 * A static util function that removes an array of values
 * from another array.
 * @param {Array} toRemove Array that contains the values to remove
 * @param {Array} allValues Array from that the values will be removed
 */
TrCollections.removeValuesFromArray = function(
  toRemove,
  allValues
  )
{
  if(toRemove && allValues)
  {
    for(i=0; i<toRemove.length; i++)
    {
      var value = toRemove[i];
      for(j=0;j<allValues.length; j++)
      {
        if(allValues[j].toLowerCase() == value.toLowerCase())
        {
          allValues.splice(j,1);
        }
      }
    }
  }
}


/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 * 
 *  http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */

function TrIntegerConverter(
  message,
  maxPrecision,
  maxScale,
  maxValue,
  minValue)
{
  this._message = message;
  this._maxPrecision = maxPrecision;
  this._maxScale = maxScale;
  this._maxValue = maxValue;
  this._minValue = minValue;

  // for debugging
  this._class = "TrIntegerConverter";
}

TrIntegerConverter.prototype = new TrConverter();

TrIntegerConverter.prototype.getFormatHint = function()
{
  return null;
}

TrIntegerConverter.prototype.getAsString = function(
  number,
  label
  )
{
  return "" + number;
}

TrIntegerConverter.prototype.getAsObject = function(
  numberString,
  label
  )
{
  return _decimalParse(numberString, 
                       this._message,
                       "org.apache.myfaces.trinidad.convert.IntegerConverter",
                       this._maxPrecision,
                       this._maxScale,
                       this._maxValue,
                       this._minValue,
                       label,
                       null);
}
function TrLongConverter(
  message,
  maxPrecision,
  maxScale,
  maxValue,
  minValue)
{
  this._message = message;
  this._maxPrecision = maxPrecision;
  this._maxScale = maxScale;
  this._maxValue = maxValue;
  this._minValue = minValue;

  // for debugging
  this._class = "TrLongConverter";
}

TrLongConverter.prototype = new TrConverter();

TrLongConverter.prototype.getFormatHint = function()
{
  return null;
}

TrLongConverter.prototype.getAsString = function(
  number,
  label
  )
{
  return "" + number;
}

TrLongConverter.prototype.getAsObject = function(
  numberString,
  label
  )
{
  return _decimalParse(numberString, 
                       this._message,
                       "org.apache.myfaces.trinidad.convert.LongConverter",
                       this._maxPrecision,
                       this._maxScale,
                       this._maxValue,
                       this._minValue,
                       label,
                       null);
}
function TrShortConverter(
  message,
  maxPrecision,
  maxScale,
  maxValue,
  minValue)
{
  this._message = message;
  this._maxPrecision = maxPrecision;
  this._maxScale = maxScale;
  this._maxValue = maxValue;
  this._minValue = minValue;

  // for debugging
  this._class = "TrShortConverter";
}

TrShortConverter.prototype = new TrConverter();

TrShortConverter.prototype.getFormatHint = function()
{
  return null;
}

TrShortConverter.prototype.getAsString = function(
  number,
  label
  )
{
  return "" + number;
}

TrShortConverter.prototype.getAsObject = function(
  numberString,
  label
  )
{
  return _decimalParse(numberString, 
                       this._message,
                       "org.apache.myfaces.trinidad.convert.ShortConverter",
                       this._maxPrecision,
                       this._maxScale,
                       this._maxValue,
                       this._minValue,
                       label,
                       null);
}
function TrByteConverter(
  message,
  maxPrecision,
  maxScale,
  maxValue,
  minValue)
{
  this._message = message;
  this._maxPrecision = maxPrecision;
  this._maxScale = maxScale;
  this._maxValue = maxValue;
  this._minValue = minValue;

  // for debugging
  this._class = "TrByteConverter";
}

TrByteConverter.prototype = new TrConverter();

TrByteConverter.prototype.getFormatHint = function()
{
  return null;
}

TrByteConverter.prototype.getAsString = function(
  number,
  label
  )
{
  return "" + number;
}

TrByteConverter.prototype.getAsObject = function(
  numberString,
  label
  )
{
  return _decimalParse(numberString, 
                       this._message,
                       "org.apache.myfaces.trinidad.convert.ByteConverter",
                       this._maxPrecision,
                       this._maxScale,
                       this._maxValue,
                       this._minValue,
                       label,
                       null);
}

function TrDoubleConverter(
  message,
  maxPrecision,
  maxScale,
  maxValue,
  minValue)
{
  this._message = message;
  this._maxPrecision = maxPrecision;
  this._maxScale = maxScale;
  this._maxValue = maxValue;
  this._minValue = minValue;

  // for debugging
  this._class = "TrDoubleConverter";
}

TrDoubleConverter.prototype = new TrConverter();

TrDoubleConverter.prototype.getFormatHint = function()
{
  return null;
}

TrDoubleConverter.prototype.getAsString = function(
  number,
  label
  )
{
  var numberString = "" + number;
  var index = numberString.indexOf(".");
  if(index != -1)
    return numberString;
  else
    return "" + number.toFixed(1);
}

TrDoubleConverter.prototype.getAsObject = function(
  numberString,
  label
  )
{
  return _decimalParse(numberString, 
                       this._message,
                       "org.apache.myfaces.trinidad.convert.DoubleConverter",
                       this._maxPrecision,
                       this._maxScale,
                       this._maxValue,
                       this._minValue,
                       label,
                       true, 
                       true);
}
function TrFloatConverter(
  message,
  maxPrecision,
  maxScale,
  maxValue,
  minValue)
{
  this._message = message;
  this._maxPrecision = maxPrecision;
  this._maxScale = maxScale;
  this._maxValue = maxValue;
  this._minValue = minValue;

  // for debugging
  this._class = "TrFloatConverter";
}

TrFloatConverter.prototype = new TrConverter();

TrFloatConverter.prototype.getFormatHint = function()
{
  return null;
}

TrFloatConverter.prototype.getAsString = function(
  number,
  label
  )
{
  var numberString = "" + number;
  var index = numberString.indexOf(".");
  if(index != -1)
    return numberString;
  else
    return "" + number.toFixed(1);
}

TrFloatConverter.prototype.getAsObject = function(
  numberString,
  label
  )
{
  return _decimalParse(numberString, 
                       this._message,
                       "org.apache.myfaces.trinidad.convert.FloatConverter",
                       this._maxPrecision,
                       this._maxScale,
                       this._maxValue,
                       this._minValue,
                       label,
                       true,
                       true);
}


function TrRangeValidator(
  maxValue,
  minValue,
  messages)
{
  this._maxValue = maxValue;
  this._minValue = minValue;
  this._messages = messages;

  // for debugging
  this._class = "TrRangeValidator";
}

TrRangeValidator.prototype = new TrValidator();
TrRangeValidator.prototype.getHints = function(
  converter
  )
{
  return _returnRangeHints(
    this._messages,
    this._maxValue,
    this._minValue,
    "org.apache.myfaces.trinidad.validator.RangeValidator.MAXIMUM_HINT",
    "org.apache.myfaces.trinidad.validator.RangeValidator.MINIMUM_HINT",
    "org.apache.myfaces.trinidad.validator.RangeValidator.RANGE_HINT",
    "hintMax",
    "hintMin",
    "hintRange"
  );
}
TrRangeValidator.prototype.validate  = function(
  value,
  label,
  converter
)
{
  string = "" + value;
  numberValue = parseFloat(string);
  var facesMessage;
  if(this._minValue != null && this._maxValue != null)
  {
    //range
    if(numberValue >= this._minValue && numberValue <= this._maxValue)
    {
      return string;
    }
    else
    {
      var key = "org.apache.myfaces.trinidad.validator.LongRangeValidator.NOT_IN_RANGE";
      if(this._messages && this._messages["range"])
      {
        facesMessage = _createCustomFacesMessage(TrMessageFactory.getSummaryString(key),
                                        this._messages["range"],
                                        label,
                                        string,
                                        ""+this._minValue,
                                        ""+this._maxValue);
      }
      else
      {
        facesMessage = _createFacesMessage(key,
                                        label,
                                        string,
                                        ""+this._minValue,
                                        ""+this._maxValue);
      }
    }
  }
  else
  {
    //only min
    if(this._minValue != null)
    {
      if(numberValue >= this._minValue)
      {
        return string;
      }
      else
      {
        var key = "org.apache.myfaces.trinidad.validator.LongRangeValidator.MINIMUM";
        if(this._messages && this._messages["min"])
        {
          facesMessage = _createCustomFacesMessage(TrMessageFactory.getSummaryString(key),
                                        this._messages["min"],
                                        label,
                                        string,
                                        ""+this._minValue);
        }
        else
        {
          facesMessage = _createFacesMessage(key,
                                        label,
                                        string,
                                        ""+this._minValue);
        }
      }
    }
    //max only
    else
    {
      if(this._maxValue  == null || numberValue <= this._maxValue)
      {
        return string;
      }
      else
      {
        var key = "org.apache.myfaces.trinidad.validator.LongRangeValidator.MAXIMUM";
        if(this._messages && this._messages["max"])
        {
          facesMessage = _createCustomFacesMessage(TrMessageFactory.getSummaryString(key),
                                        this._messages["max"],
                                        label,
                                        string,
                                        ""+this._maxValue);
        }
        else
        {
          facesMessage = _createFacesMessage(key,
                                        label,
                                        string,
                                        ""+this._maxValue);
        }
      }
    }
  }
  throw new TrConverterException(facesMessage);
}

function TrLengthValidator(
  maxValue,
  minValue,
  messages)
{
 
  this._maxValue = maxValue;
  this._minValue = minValue;
  this._messages = messages;

  // for debugging
  this._class = "TrLengthValidator";
}

TrLengthValidator.prototype = new TrValidator();
TrLengthValidator.prototype.getHints = function(
  converter
  )
{
  return _returnRangeHints(
    this._messages,
    this._maxValue,
    this._minValue,
    "org.apache.myfaces.trinidad.validator.LengthValidator.MAXIMUM_HINT",
    "org.apache.myfaces.trinidad.validator.LengthValidator.MINIMUM_HINT",
    (this._minValue == this._maxValue)
      ? "org.apache.myfaces.trinidad.validator.LengthValidator.EXACT_HINT"
      : "org.apache.myfaces.trinidad.validator.LengthValidator.RANGE_HINT",
    "hintMax",
    "hintMin",
    // The server always sends down "hintRange" for exact or non-exact
    "hintRange"
  );
}
TrLengthValidator.prototype.validate  = function(
  value,
  label,
  converter
)
{

  var string = "" + value;
  var length = string.length;
  
  // If validation succeeds, return
  if (length >= this._minValue &&
     ((this._maxValue == null) || (length <= this._maxValue)))
  {
    return string;
  }
  else
  {
    if ((this._minValue > 0) && (this._maxValue != null))
    {
      var exact = (this._minValue == this._maxValue);
      var key = exact
        ? "org.apache.myfaces.trinidad.validator.LengthValidator.EXACT"
        : "org.apache.myfaces.trinidad.validator.LengthValidator.NOT_IN_RANGE";
      var facesMessage;
      var customKey = "range";

      if(this._messages && this._messages[customKey])
      {
        facesMessage = _createCustomFacesMessage(TrMessageFactory.getSummaryString(key),
                                        this._messages[customKey],
                                        label,
                                        string,
                                        ""+this._minValue,
                                        ""+this._maxValue);
      }
      else
      {
        facesMessage = _createFacesMessage(key,
                                        label,
                                        string,
                                        ""+this._minValue,
                                        ""+this._maxValue);
      }
      throw new TrConverterException(facesMessage);
    }
    else if (length < this._minValue) //too short
    {
      var key = "org.apache.myfaces.trinidad.validator.LengthValidator.MINIMUM";
      var facesMessage;
      if(this._messages && this._messages["min"])
      {
        facesMessage = _createCustomFacesMessage(TrMessageFactory.getSummaryString(key),
                                        this._messages["min"],
                                        label,
                                        string,
                                        ""+this._minValue);
      }
      else
      {
        facesMessage = _createFacesMessage(key,
                                        label,
                                        string,
                                        ""+this._minValue);
      }
      throw new TrConverterException(facesMessage);
    }
    else // too long
    {
      var key = "org.apache.myfaces.trinidad.validator.LengthValidator.MAXIMUM";
      var facesMessage;
      if(this._messages && this._messages["max"])
      {
        facesMessage = _createCustomFacesMessage(TrMessageFactory.getSummaryString(key),
                                        this._messages["max"],
                                        label,
                                        string,
                                        ""+this._maxValue);
      }
      else
      {
        facesMessage = _createFacesMessage(key,
                                        label,
                                        string,
                                        ""+this._maxValue);
      }
      throw new TrConverterException(facesMessage);
    }
  }
}

function TrDateTimeRangeValidator(
  maxValue,
  minValue,
  messages)
{
  this._maxValue = maxValue;
  this._minValue = minValue;
  this._messages = messages;
  // for debugging
  this._class = "TrDateTimeRangeValidator";
}

TrDateTimeRangeValidator.prototype = new TrValidator();
TrDateTimeRangeValidator.prototype.getHints = function(
  converter
  )
{
  var max = null;
  var min = null;
  if (this._maxValue)
    max = this._maxValue;
  if (this._minValue)
    min = this._minValue;

  return _returnRangeHints(
    this._messages,
    max,
    min,
    "org.apache.myfaces.trinidad.validator.DateTimeRangeValidator.MAXIMUM_HINT",
    "org.apache.myfaces.trinidad.validator.DateTimeRangeValidator.MINIMUM_HINT",
    "org.apache.myfaces.trinidad.validator.DateTimeRangeValidator.RANGE_HINT",
    "hintMax",
    "hintMin",
    "hintRange"
  );
}
TrDateTimeRangeValidator.prototype.validate  = function(
  value,
  label,
  converter
)
{
  dateTime = value.getTime();
  var facesMessage;
  //range
  if(this._minValue && this._maxValue)
  {
    try
    {
      minDate = (converter.getAsObject (this._minValue)).getTime();
      maxDate = (converter.getAsObject (this._maxValue)).getTime();
    }
    catch (e)
    {
      // Make the validator lenient: let the server convert/validate if 
      // client conversion fails
      return value;
    }
    if(dateTime >= minDate && dateTime <= maxDate)
    {
      return value;
    }
    else
    {
      var key = "org.apache.myfaces.trinidad.validator.DateTimeRangeValidator.NOT_IN_RANGE";
      if(this._messages && this._messages["range"])
        {
          facesMessage = _createCustomFacesMessage(TrMessageFactory.getSummaryString(key),
                                        this._messages["range"],
                                        label,
                                        ""+converter.getAsString(value),
                                        ""+this._minValue,
                                        ""+this._maxValue);
        }
      else
      {
          facesMessage = _createFacesMessage(key,
                                        label,
                                        ""+converter.getAsString(value),
                                        ""+this._minValue,
                                        ""+this._maxValue);
      }
    }
  }
  else
  {
    //only min
    if(this._minValue)
    {
      try
      {
        minDate = (converter.getAsObject (this._minValue)).getTime();
      }
      catch (e)
      {
        // Make the validator lenient: let the server convert/validate if 
        // client conversion fails
        return value;
      }

      if(dateTime >= minDate)
      {
        return value;
      }
      else
      {
        var key = "org.apache.myfaces.trinidad.validator.DateTimeRangeValidator.MINIMUM";
      if(this._messages && this._messages["min"])
        {
          facesMessage = _createCustomFacesMessage(TrMessageFactory.getSummaryString(key),
                                        this._messages["min"],
                                        label,
                                        ""+converter.getAsString(value),
                                        ""+this._minValue);
        }
      else
      {
          facesMessage = _createFacesMessage(key,
                                        label,
                                        ""+converter.getAsString(value),
                                        ""+this._minValue);
      }
      }
    }
    //max only
    else if(this._maxValue)
    {
      try
      {
      maxDate = (converter.getAsObject (this._maxValue)).getTime();
        
      }
      catch (e)
      {
        // Make the validator lenient: let the server convert/validate if 
        // client conversion fails
        return value;
      }
      if(dateTime <= maxDate)
      {
        return value;
      }
      else
      {
        var key = "org.apache.myfaces.trinidad.validator.DateTimeRangeValidator.MAXIMUM";
        if(this._messages && this._messages["max"])
        {
          facesMessage = _createCustomFacesMessage(TrMessageFactory.getSummaryString(key),
                                        this._messages["max"],
                                        label,
                                        ""+converter.getAsString(value),
                                        ""+this._maxValue);
        }
        else
        {
          facesMessage = _createFacesMessage(key,
                                        label,
                                        ""+converter.getAsString(value),
                                        ""+this._maxValue);
        }
      }
    }
    else
    {
      //no min/max specified
      return value;
    }
  }
  throw new TrConverterException(facesMessage);
}

function TrDateRestrictionValidator(
  weekdaysValue,
  monthValue,
  messages)
  
{
  this._weekdaysValue = weekdaysValue;
  this._monthValue = monthValue;
  this._messages = messages;
  this._weekdaysMap = {'2':'tue','4':'thu','6':'sat','1':'mon','3':'wed','5':'fri','0':'sun'};
  this._translatedWeekdaysMap = {'sun':'0','mon':'1','tue':'2','wed':'3','thu':'4','fri':'5','sat':'6'};
  this._monthMap = {'2':'mar','4':'may','9':'oct','8':'sep','11':'dec','6':'jul','1':'feb','3':'apr','10':'nov','7':'aug','5':'jun','0':'jan'};
  this._translatedMonthMap = {'jan':'0','feb':'1','mar':'2','apr':'3','may':'4','jun':'5','jul':'6','aug':'7','sep':'8','oct':'9','nov':'10','dec':'11'};

  // for debugging
  this._class = "TrDateRestrictionValidator";
}

TrDateRestrictionValidator.prototype = new TrValidator();
TrDateRestrictionValidator.prototype.getHints = function(
  converter
  )
{
  var allWeekdays = ['mon','tue','wed','thu','fri','sat','sun'];
  var allMonth = ['jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec'];
  
  //if needed, remove the submitted values, which are invalid, to display only the valid ones
  if(this._weekdaysValue)
    TrCollections.removeValuesFromArray(this._weekdaysValue, allWeekdays);
  if(this._monthValue)
  TrCollections.removeValuesFromArray(this._monthValue, allMonth);
  
  return _returnHints(
    this._messages,
    !this._weekdaysValue ? this._weekdaysValue : this._translate(allWeekdays, this._translatedWeekdaysMap, converter.getLocaleSymbols().getWeekdays()),
    !this._monthValue ? this._monthValue : this._translate(allMonth, this._translatedMonthMap, converter.getLocaleSymbols().getMonths()),
    "org.apache.myfaces.trinidad.validator.DateRestrictionValidator.WEEKDAY_HINT",
    "org.apache.myfaces.trinidad.validator.DateRestrictionValidator.MONTH_HINT",
    "hintWeek",
    "hintMonth"
  );
}

TrDateRestrictionValidator.prototype._translate = function(
  values,
  map,
  valueArray
  )
{
  if(values)
  {
    var translatedValues = new Array();
    var valuesAsArray = eval(values);
    for(i = 0; i<valuesAsArray.length; i++)
    {
      translatedValues.push(valueArray[map[valuesAsArray[i].toLowerCase()]]);
    }
    return eval(translatedValues);
  }
  else
  {
    return values;
  }
}
TrDateRestrictionValidator.prototype.validate  = function(
  value,
  label,
  converter
)
{
  submittedDay = value.getDay();
  weekDaysArray = eval(this._weekdaysValue);
  if(weekDaysArray)
  {
    var dayString = this._weekdaysMap[submittedDay];
    for(var i = 0; i < weekDaysArray.length; ++i)
    {
      if(weekDaysArray[i].toLowerCase() == dayString)
      {
        var allWeekdays = ['mon','tue','wed','thu','fri','sat','sun'];
        TrCollections.removeValuesFromArray(this._weekdaysValue, allWeekdays);
        var days = _trToString(this._translate(allWeekdays, this._translatedWeekdaysMap, converter.getLocaleSymbols().getWeekdays()));

        var facesMessage;
        var key = "org.apache.myfaces.trinidad.validator.DateRestrictionValidator.WEEKDAY";
        if(this._messages && this._messages["days"])
        {
          facesMessage = _createCustomFacesMessage(TrMessageFactory.getSummaryString(key),
                                        this._messages["days"],
                                        label,
                                        ""+converter.getAsString(value),
                                        days);
        }
        else
        {
          facesMessage = _createFacesMessage(key,
                                        label,
                                        ""+converter.getAsString(value),
                                        days);
        }
        throw new TrConverterException(facesMessage);
      }
    }
  }
  
  submittedMonth = value.getMonth();
  monthArray = eval(this._monthValue);
  if(monthArray)
  {
    var monthString = this._monthMap[submittedMonth];
    for(var i = 0; i < monthArray.length; ++i)
    {
      if(monthArray[i].toLowerCase() == monthString)
      {
        var allMonth = ['jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec'];
        TrCollections.removeValuesFromArray(this._monthValue, allMonth);
        var month = _trToString(this._translate(allMonth, this._translatedMonthMap, converter.getLocaleSymbols().getMonths()));
        
        var facesMessage;
        var key = "org.apache.myfaces.trinidad.validator.DateRestrictionValidator.MONTH";
        if(this._messages && this._messages["month"])
        {
          facesMessage = _createCustomFacesMessage(TrMessageFactory.getSummaryString(key),
                                        this._messages["month"],
                                        label,
                                        ""+converter.getAsString(value),
                                        month);
        }
        else
        {
          facesMessage = _createFacesMessage(key,
                                        label,
                                        ""+converter.getAsString(value),
                                        month);
        }
        throw new TrConverterException(facesMessage);
      }
    }
  }
  return value;
}

/**
 * @param parsefloat 
 *   null  ==> we expect the input to be an integral type, throw exception otherwise
 *   false ==> integerOnly="true", parse input as an integer
 *   true  ==> parse input as a float
 */
function _decimalParse(
  numberString,
  message,
  standardKey,
  maxPrecision,
  maxScale,
  maxValue,
  minValue,
  label,
  parsefloat,
  ignoreLocaleSymbols
  )
{
  // The following are from the javadoc for TrNumberConverter
  // If the specified String is null, return a null. Otherwise, trim leading and trailing whitespace before proceeding.
  // If the specified String - after trimming - has a zero length, return null.
  if (numberString == null)
    return null;
    
  numberString = TrUIUtils.trim(numberString);
  if (numberString.length == 0)
    return null
    
  var facesMessage = null;        

  // Get LocaleSymbols (from Locale.js)
  var symbols = getLocaleSymbols();
  if (symbols && (ignoreLocaleSymbols != true))
  {
    // We don't want leading or trailing grouping separators
    var grouping = symbols.getGroupingSeparator();
    if ((numberString.indexOf(grouping) == 0) ||
        (numberString.lastIndexOf(grouping) ==  (numberString.length - 1)))
    {
      facesMessage =  _createFacesMessage( standardKey+".CONVERT",
                                        label,
                                        numberString);
                                        
      throw new TrConverterException(facesMessage);
    }

    if (grouping == "\xa0"){
      var normalSpace = new RegExp("\\ " , "g");
      numberString = numberString.replace(normalSpace, "\xa0");
    }
    
    // Remove the thousands separator - which Javascript doesn't want to see
    
    //is this a i18n bug?...
    //see TRINIDAD-2
    var thousands = new RegExp("\\" + grouping, "g");
    numberString = numberString.replace(thousands, "");
    // Then change the decimal separator into a period, the only
    // decimal separator allowed by JS
    var decimal = new RegExp("\\" + symbols.getDecimalSeparator(),  "g");
    numberString = numberString.replace(decimal, ".");
  }


  // OK; it's non-empty.  Now, disallow exponential
  // notation, and then use some JS magic to exclude
  // non-numbers
  if ((numberString.indexOf('e') < 0) &&
      (numberString.indexOf('E') < 0) &&
      (((numberString * numberString) == 0) ||
       ((numberString / numberString) == 1)))
  {
    var result = null;
    var floater = false;
    if (parsefloat != null)
    {
      // Why parseInt(parseFloat(numberString))? Because the server NumberConverter behaves the same 
      // way as parseFloat. Note the following:
      // parseInt interprets octal and hex:
      //   alert(parseInt("0xA")); // returns 10
      //   alert(parseInt("008")); // returns 0, as it stops parsing octal at the first invalid character, 8
      // parseFloat interprets neither octal nor hex:
      //   alert(parseFloat("0xA")); // returns 0, as it stops parsing decimal at the first invalid character, x
      //   alert(parseFloat("008")); // returns 8
      result = parsefloat ? parseFloat(numberString) : parseInt(parseFloat(numberString));
    }
    else
    {
      result = parseInt(numberString);
      if (Math.abs(result) < Math.abs(parseFloat(numberString)))
      {
        //a non-floating converter was the caller;
        floater = true;
      }
    }
    if (!floater && !isNaN(result))
    {
      var integerDigits = numberString.length;
      var fractionDigits = 0;

      var sepIndex = numberString.lastIndexOf('.');
      if (sepIndex != -1)
      {
        integerDigits = sepIndex;
        fractionDigits = parseInt(numberString.length - parseInt(sepIndex + 1));
      }
      
      var messageKey;
      var rangeLimit;
      //not true for float/double converter
      if ((maxValue != null) &&
          (result  > maxValue))
      {
        messageKey = standardKey+".MAXIMUM";
        rangeLimit = maxValue;
      }
      else if ((minValue != null) &&
               (result  < minValue))
      {
        messageKey = standardKey+".MINIMUM";
        rangeLimit = minValue;
      }

      if (messageKey)
      {
        facesMessage = _createFacesMessage(messageKey,
                                      label,
                                      numberString,
                                      ""+rangeLimit);

        throw new TrConverterException(facesMessage);
      }
      return result;
    }
  }
  var usedKey = null;
  var custom = false;
  if(standardKey.indexOf("NumberConverter")==-1)
  {
    usedKey = standardKey+".CONVERT";
  }
  else
  {
    usedKey = standardKey+".CONVERT_NUMBER";
    if(message && message["number"])
    {
      facesMessage = _createCustomFacesMessage(TrMessageFactory.getSummaryString(usedKey),
                                        message["number"],
                                        label,
                                        numberString);
      custom = true;
    }
  }
  if(!custom)
  {
    facesMessage = _createFacesMessage( usedKey,
                                        label,
                                        numberString);
  }

  throw new TrConverterException(facesMessage);
}

function TrRegExpValidator(
  pattern,
  messages
  )
{  
  this._pattern  = pattern;
  this._messages = messages;
  this._class = "TrRegExpValidator";
}

TrRegExpValidator.prototype = new TrValidator();
TrRegExpValidator.prototype.getHints = function(
  converter
  )
{
  var hints = null;
  if(this._messages["hint"])
  {
    hints = new Array();
    hints.push(TrMessageFactory.createCustomMessage(
      this._messages["hint"],
      ""+this._pattern)
    );
  }
  return hints;
}
TrRegExpValidator.prototype.validate  = function(
  parseString,
  label,
  converter
  )
{
  //For some reason when using digits as input values 
  // parseString becomes a integer type, so get away with it.  
  parseString = parseString + '';
  
  // We intend that the pattern provided is matched exactly
  var exactPattern = "^(" + this._pattern + ")$";

  var matchArr = parseString.match(exactPattern); 
        
  if ((matchArr != (void 0)) && (matchArr[0] == parseString))
  {
    return parseString;
  }
  else
  {
    var key = "org.apache.myfaces.trinidad.validator.RegExpValidator.NO_MATCH";
    var facesMessage;
    if(this._messages && this._messages["detail"])
    {
      facesMessage = _createCustomFacesMessage(
                                         TrMessageFactory.getSummaryString(key),
                                         this._messages["detail"],
                                         label,
                                         parseString,
                                         this._pattern);
    }
    else
    {
      facesMessage = _createFacesMessage(key,
                                         label,
                                         parseString,
                                         this._pattern);                                          
    }
    throw new TrValidatorException(facesMessage); 
  }
}

function _returnRangeHints(
  messages,
  max,
  min,
  maxKey,
  minKey,
  rangeKey,
  maxHint,
  minHint,
  rangeHint
)
{
  
  //we have both, max and min, so we only use the range Hint
  if(max != null && min != null)
  {
    var hints = new Array();
    if(messages && messages[rangeHint])
    {
      hints.push(
        TrMessageFactory.createCustomMessage(
        messages[rangeHint],
        ""+min,
        ""+max)
      );
    }
    else
    {
      hints.push(
        TrMessageFactory.createMessage(
        rangeKey,
        ""+min,
        ""+max)
      );
    }
    return hints;
  }
  
  return _returnHints(
    messages,
    max,
    min,
    maxKey,
    minKey,
    maxHint,
    minHint
  );
  
}

function _trToString(param) 
{
  if (Array.prototype.isPrototypeOf(param))
  {
    return param.join(", ");
  }
  else
  {
    return "" + param;
  }
}

function _returnHints(
  messages,
  max,
  min,
  maxKey,
  minKey,
  maxHint,
  minHint
)
{
  var hints;
  if (max != null)
  {
    hints = new Array();
    if (messages && messages[maxHint])
    {
      hints.push(
        TrMessageFactory.createCustomMessage(
          messages[maxHint],
          _trToString(max))
      );
    }
    else
    {
      hints.push(
        TrMessageFactory.createMessage(
          maxKey,
          _trToString(max))
      );
    }
    
  }
  if (min != null)
  {
    if (!hints)
    {
      hints = new Array();
    }
    if (messages && messages[minHint])
    {
      hints.push(
        TrMessageFactory.createCustomMessage(
          messages[minHint],
          _trToString(min))
       );
    }
    else
    {
      hints.push(
        TrMessageFactory.createMessage(
          minKey,
          _trToString(min))
       );
    }
  }
  return hints;
}

/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 * 
 *  http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */

// _dfsv(): Date Field Set Value function.  
function _dfsv(
  dateField,
  newValue,
  serverOffsetInMins
  )
{
  // Make sure we have valid values
  if ((dateField == (void 0)) || (newValue == (void 0)))
    return;

  // Hold on to the initial date value
  var baseDate = new Date(newValue);
  
  // We need to compare the time zone that is on the client with the time
  // zone that came from the localeContext on the server and adjust if
  // necessary
  var tzd = _getLocaleTimeZoneDifference2(baseDate, serverOffsetInMins);
    
  // _getTimePortion below expects that newValue is time zero (midnight) on the given
  // day. The code changes to handle the new Daylight Savings Time dates (as of
  // 2007, DST was extended by a couple of weeks) broke the calculations for
  // 2006 and prior on Windows. This yields an off-by-one-hour problem on the
  // day after the DST switch. Here we adjust for that.
   newValue = _dfGetMidnight(newValue + tzd);  
    
  // Add back in the time
  // offset,since we don't want to overwrite the user's time when 
  // they pick a new date from the calendar. 
  newValue += _getTimePortion(dateField);

  var newDate = new Date(newValue);
  
  // If the date used for the time-of-day calculation is different from the
  // timezone (daylight savings or standard time) of the base date, then we'll
  // be off by an hour. Here we adjust for that hour. Note this is different
  // from the check in the _getTimePortion method, that only checks for a
  // timezone change on the particular day used for the calculation. Both
  // checks are needed.
  var tzDiffOffset = _getTimezoneDiff(baseDate, newDate);
  if (tzDiffOffset != 0)
    newDate = new Date (newValue - tzDiffOffset);

  //
  // get the format to use to format the result
  //
  var format = _getDateFieldFormat(dateField);
    
  // update the contents of the text field
  var oldValue = dateField.value;
  var newValue = format.getAsString(newDate);

  if (dateField.value != newValue)
  {
    // bug 2275982
    // trigger onchange programatically
    if (dateField.onchange != (void 0))
    {
      // The IE event delivery mechanism for built-in events like onchange
      // automatically sets the window.event property for inspection by
      // event handlers.
      // When we synthesize this onchange event here, we must ensure that
      // window.event is as close as possible to the value of window.event
      // that would be visible to the onchange handler if it had been triggered 
      // by user interaction, like moving focus away from the field.
      // Updating window.event directly triggers a JS error in IE, so we must
      // use the built-in event delivery mechanism instead.
      // Here, we attach a handler to the IE-specific propertychange event,
      // which will fire when any property is changed, not just the value.
      // In our handler, we only respond to a change in the value property,
      // by detaching the handler and delivering the onchange event.
      // Now, the value for window.event during the onchange handler execution
      // is the property change event but it has the right values for source,
      // target, etc.
      if (_agent.isIE)
      {
        // attach the value change listener
        dateField.onpropertychange = function()
        {
          var event = window.event;
          if (event.propertyName == 'value')
          {
            // detach the value change listener
            dateField.onpropertychange = function(){};
              
            dateField.onchange(event);
          }
        }

        dateField.value = newValue;
      }
      else
      {
        dateField.value = newValue;

        var event = new Object();
        event.type = 'change';
        event.target = dateField;
        dateField.onchange(event);
      }
    }
    else // no onchange handler
    {
      dateField.value = newValue;
    }
  }

  dateField.select();
  dateField.focus();
}

/**
 * The long 'date' value rendered by the server doesn't have the client's 
 * daylight saving information. It is just wrt the current timeZone offset 
 * of the client. So add the daylight saving offset before calculating the 
 * actual date.
 */
function _getDayLightSavOffset(newValue)
{
  var currDate = new Date();
  var dstDate = new Date(newValue);
  
  var dlsOffset = dstDate.getTimezoneOffset() - currDate.getTimezoneOffset();
  return (dlsOffset * 60 * 1000);
}

function _returnCalendarValue(
  closingWindow,
  event
  )
{
  // extract the return value
  var newValue = closingWindow.returnValue;
  
  if (newValue != (void 0))
  {
    var dateField = closingWindow._dateField; 
    // See below!
    if (dateField == (void 0))
    {
      dateField = _savedField1879034;
    }

    var serverOffset = closingWindow.serverOffsetInMins;
    if (serverOffset != (void 0))
      _dfsv(dateField, newValue, serverOffset);
    else
      _dfsv(dateField, newValue);    
  }
}

function _returnPopupCalendarValue(
  props,
  value
  )
{
  // Callback method registered with the popup
  // 'props' contains the name of the target form & field to populate
  if (value != (void 0))
  {
    var formName = props['formName'];
    var fieldName = props['fieldName'];
    var dateField = document.forms[formName][fieldName];
    _dfsv(dateField, value);
  }
}


/**
 * Private function for launching the date picker
 */
function _ldp(
  formName,
  nameInForm,
  usePopup,
  minValue,
  maxValue,
  destination
  )
{
  var dateField = document.forms[formName][nameInForm];
  var oldValue = _dfgv(dateField);

  if (!oldValue)
  {
    // if the parse failed, then create a new Date object.
    oldValue = new Date();
  }

  // default the destination to the calendar dialog destination
  if (!destination)
  {
    destination = _jspDir + _getQuerySeparator(_jspDir);

    //Only use frame redirect for non popup date picker
    if (usePopup)
      destination += "_t=cd";
    else
      destination += "_t=fred&_red=cd";
  }
  else
  {
    // since we need to redirect, replace the last portion of the URL with
    // the redirect JSP
    var endOfUrl = destination.lastIndexOf('?');
    var urlArgs  = "";
  
    if (endOfUrl == -1)
    {
      endOfUrl = destination.length;
    }
    else
    {
      urlArgs = destination.substr(endOfUrl + 1);
    }

    var startOfLastPart = destination.lastIndexOf('/', endOfUrl);
  
    var newDest = destination.substring(0, startOfLastPart + 1);
    newDest += _jspDir + _getQuerySeparator(_jspDir);
    newDest += urlArgs;

    // add the correct first param separator
    newDest += _getQuerySeparator(newDest);
    newDest += "_t=fred";

    var redirect = destination.substring(startOfLastPart + 1, endOfUrl);

    destination = newDest;

    // add in the redirect
    destination += "&redirect=" + escape(redirect);
  }
  
  // add the current time in Millis since 1970
  var timeval = oldValue.getTime() - _getLocaleTimeZoneDifference(oldValue);
  destination += "&value=" + timeval
  
    
  // add the locale
  destination += "&loc=" + _locale;
    
  // and the character set encoding
  if (window["_enc"])
  {
    destination += "&enc=" + _enc;
  }

  //
  // add on min and max value attributes
  //
  if (minValue != (void 0))
  {
    destination += "&minValue=" + minValue; 
  }
  
  if (maxValue != (void 0))
  {
    destination += "&maxValue=" + maxValue; 
  }

  if (usePopup)
  {
    // Open the dialog passing callback details
    TrPopupDialog._launchDialog(
      destination,
      {},
      _returnPopupCalendarValue,
      { 'formName':formName, 'fieldName':nameInForm });
  }
  else
  {
    // Open the window;  we used to name it "calendar", but
    // that's a common enough name that we hit bug 2807778
    var calWindow = openWindow(self,
                               destination,
                               'uix_2807778',
                               {width:350, height:370},
                               true,
                               void 0,
                               _returnCalendarValue);
    
    // save the date field on the calendar window for access
    // from event handler
    calWindow._dateField = dateField;
  
    // And, for bug 1879034, stash it on a JS variable.  It
    // seems that IE sometimes has already blown away the values
    // on "calWindow"!
    _savedField1879034 = dateField;  
  }
}

// _dfgv(): Date Field Get Value function
// Returns the value of the dateField as a Date object
// or null if there was an error.
function _dfgv(dateField)
{
  if (dateField.value != "")
  {
    try{
      var value = _getDateFieldFormat(dateField).getAsObject(dateField.value);      
      return value;
    }
    catch (e)
    {
      // no-op
    }    
  }

  return null;
}


/* 
 * Returns the time-only portion of the dateField in ms.
 */
function _getTimePortion(dateField)
{
  var oldValue = _dfgv(dateField);

  // If the date field doesn't have a value, use the
  // current time.
  if (!oldValue)
    oldValue = new Date();

  // get just the time portion in milliseconds from the date field.
  // First, get just the date portion of the oldValue 
  // (the value in the date field). 
  // Then subtract the date-only date from the date-time date to get
  // the time only portion. We'll add this time back in after the
  // user picks a date from the calendar. This way the time 
  // in the field, if any, will be preserved, and empty fields
  // will default to the current time.
  var oldValueDateOnly = new Date ( oldValue.getFullYear(), 
                                    oldValue.getMonth(), 
                                    oldValue.getDate());
      
  // get only the time portion of the date in the field.
  var diff = oldValue-oldValueDateOnly;

  // If the timezone changed today, subtract out the offset.
  // This will only happen on the day that we switch from standard time to
  // daylight savings time, or back again.
  diff -= _getTimezoneDiff(oldValue, oldValueDateOnly);

  return diff;
}

/**
 * compare the time zone that is on the client with the time zone that
 * came from the localeContext on the server, and return the difference.
 * This can be used to adjust the date/time value that will be displayed in
 * the date field to use the timezone set on the locale context on the 
 * server instead of the timezone we get from javascript's getTimezoneOffset.
 * see bug 3167883
 * TRINIDAD-1349:_uixLocaleTZ stores the timezone offset of the server at
 * the time the page was displayed (Current time), and currentDateTZOffset
 * is the timezone offset of client at the current time as well. However,
 * the timezone offsets for both client and server can differ for the
 * date that was picked due to daylight savings rules. For example, the
 * current time is 3 Dec 2008 and the server is in PST (UTC -8) and 
 * client is in Perth (AWDT, UTC + 9) so the difference is 17h. But if 
 * the user picks Apr 25, the server is actually in PDT then (UTC-7) and
 * the client in AWST (UTC +8) so the difference is actually 15h. The original 
 * code would subtract 17h, which would cause the resulting date to move
 * to the previous day. * 
 */
function _getLocaleTimeZoneDifference2(clientDate, serverOffset)
{
  // timeZoneOffset in javascript appears to give
  // the wrong sign, so I am switching it.
  // the timeZoneOffset is in minutes.
  var clientOffset = clientDate.getTimezoneOffset() * -1;
  var tzOffsetDiff = 0;
  if (serverOffset != void(0))
    tzOffsetDiff = (serverOffset - clientOffset)*60*1000;
  else if (_uixLocaleTZ != (void(0)))
    tzOffsetDiff = (_uixLocaleTZ - clientOffset)*60*1000;
  
  return tzOffsetDiff;
}

/**
 * Just find the difference in millis between the timezone of two dates
 */
function _getTimezoneDiff(oldDate, newDate)
{
  return (oldDate.getTimezoneOffset() - newDate.getTimezoneOffset()) * 60000;
}

/**
 * _dfGetMidnight: Date Field Get Midnight
 *                 Returns the date val for midnight on the date given by the
 *                 input dateVal.
 */
function _dfGetMidnight(dateVal)
{
  var baseDate = new Date(dateVal);
  // Just zero out the date
  baseDate.setHours(0);
  baseDate.setMinutes(0);
  baseDate.setSeconds(0);
  baseDate.setMilliseconds(0);

  // and return the corresponding date value
  return baseDate.getTime();
}

/**
 * _dbb(): Date Field Blur handler
 *
 * Parameters:
 * - dateField is the date field object
 * - calendarID is the ID of the inline calendar 
 *     associated with dateField
 */
function _dfb(dateField, calendarID)
{
  
   _fixDFF(dateField);

//  if (calendarID != (void 0))
//    _calActiveDateFields[calendarID] = null;

}

/**
 * _dbf(): Date Field Focus handler
 *
 * Parameters:
 * - dateField is the date field object
 * - calendarID is the ID of the inline calendar 
 *     associated with dateField
 */
function _dff(dateField, calendarID)
{
  _dfa(dateField, calendarID);
}

/**
 * _dba(): Date Field Activate
 * 
 * Makes the specified dateField the "active" dateField
 * for the calendar
 *
 * Parameters:
 * - dateField is the date field object or id
 * - calendarID is the ID of the inline calendar 
 *     associated with dateField
 */
function _dfa(dateField, calendarID)
{
  if (calendarID != (void 0))
  {
    if (window._calActiveDateFields == (void 0))
      window._calActiveDateFields = new Object();

    if (typeof(dateField) == "string")
    {
      dateField = _getElementById(document, dateField);
    }

    window._calActiveDateFields[calendarID] = dateField;
  }
}

/**
 * _calsd(): Calendar Select Date function.
 *
 * Parameters:
 * - source is the id of the calendar
 * - value is the date value to select
 */
function _calsd(source, value, serverOffsetInMins)
{

  if (window._calActiveDateFields != (void 0))
  {
    var dateField = window._calActiveDateFields[source];

    if (dateField)
      _dfsv(dateField, value, serverOffsetInMins);
  }

  return false;
}

function _updateCal(choice,url,p)
{

  url += ('&scrolledValue='+choice.options[choice.selectedIndex].value);
  if (p) 
    _firePartialChange(url);
  else 
    document.location.href=url;
}


function _doCancel()
{
  var dialog = parent.TrPopupDialog.getInstance();
  if (dialog)
  {
    dialog.returnValue = (void 0);
    //TODO - Need Cleaner way to close dialogs using via getInstance()
    parent.TrPopupDialog._returnFromDialog();
  }
  else
  {
    top.returnValue = (void 0);
    top.close();
  }
  return false;
}

function _selectDate(dateTime, serverOffsetInMins)
{
  var dialog = parent.TrPopupDialog.getInstance();
  if (dialog)
  {
    dialog.returnValue = dateTime;
    dialog.serverOffsetInMins = serverOffsetInMins;
    //TODO - Need Cleaner way to close dialogs using via getInstance()
    parent.TrPopupDialog._returnFromDialog();
  }
  else
  {
    top.returnValue = dateTime;
    top.serverOffsetInMins = serverOffsetInMins;    
    top._unloadADFDialog(window.event);
    top.close();
  }
  return false;
}

// Holds the date dialog box when open
var _DATE_DIALOG;

var _savedField1879034;

/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 * 
 *  http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */
function _getDateFieldFormat(dateField)
{
  var name = dateField.name;
  if (name && _dfs)
  {
    var format = _dfs[name];
    if (_dl)
    {
      var locale = _dl[name];
      return new TrDateTimeConverter (format, locale);
    }
   return new TrDateTimeConverter(format);
  }

  return new TrDateTimeConverter();
}

function _fixDFF(dateField)
{
  var format = _getDateFieldFormat(dateField);

  if (dateField.value != "")
  {
    try
    {
      var value = format.getAsObject(dateField.value);
      if (value != null)
        dateField.value = format.getAsString(value);
    }
    catch (e)
    {
      // no-op
    }
  }
}



/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 * 
 *  http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */

// External variables used:
//  _df2DYS: Sets the two-digit year start.

var _AD_ERA = null;


function _getADEra()
{
  if (_AD_ERA == null)
  {
    _AD_ERA = new Date(0);
    _AD_ERA.setFullYear(1);
  }
  
  return _AD_ERA;
}


/**
 * Determine whether the parsed time is a strictly parsed value.
 */
function _isStrict(
  parseContext,
  parsedTime)
{
  var checks = ["FullYear", "Month", "Date", "Hours", "Minutes",
                "Seconds", "Milliseconds"];

  for (var i=0; i < checks.length; i++)
  {
    var parsed = "parsed" + checks[i];

    if (parseContext[parsed] != null &&
        parseContext[parsed] != parsedTime["get" + checks[i]]())
    {
      // failure for strict parsing
      return false;
    }
  }

  return true;
}


/**
 * Clump up similar runs of pattern characters from the format patter and
 * call the subfunction for each result.  Return whether the clumping
 * succeeded.
 */
function _doClumping(
  formatPattern,
  localeSymbols,
  subFunction,
  param,
  outValue
  )
{  
  var formatLength = formatPattern.length;
  var inQuote      = false;
  var kindCount    = 0;
  var lastChar     = void 0;
  var startIndex   = 0;
  
  for (var i = 0; i < formatLength; i++)
  {
    var currChar = formatPattern.charAt(i);
    
    if (inQuote)
    {
      if (currChar == "\'")
      {
        inQuote = false;
        
        // handle to single quotes in a row as escaping the quote
        // by not skipping it when outputting
        if (kindCount != 1)
        {
          startIndex++;
          kindCount--;
        }

        // output the quoted text
        if (!subFunction(formatPattern,
                         localeSymbols,
                         "\'",
                         startIndex,
                         kindCount,
                         param,
                         outValue))
        {
          // failure
          // alert("failure at " + startIndex + " with " + lastChar);
          return false;
        }
        
        kindCount = 0;
        lastChar  = void 0;
      }
      else
      {
        // keep adding characters to the escaped String
        kindCount++;
      }
    }
    else
    {
      // the characters that we are collecting have changed
      if (currChar != lastChar)
      {
        if (kindCount != 0)
        {       
          // output the previously collected string
          if (!subFunction(formatPattern,
                           localeSymbols,
                           lastChar,
                           startIndex,
                           kindCount,
                           param,
                           outValue))
          {
            // failure
            //alert("failure at " + startIndex + " with " + lastChar);
            return false;
          }
          
          kindCount = 0;
          lastChar  = void 0;
        }
        
        if (currChar == '\'')
        {
          inQuote = true;
        }
  
        startIndex = i;      
        lastChar = currChar;
      }

      // keep collecting this kind of character together
      kindCount++;
    }   
  }
  
  // output any left over substring being collected
  if (kindCount != 0)
  {
    if (!subFunction(formatPattern,
                     localeSymbols,
                     lastChar,
                     startIndex,
                     kindCount,
                     param,
                     outValue))
    {
      // failure
      //alert("failure at " + startIndex + " with " + lastChar);
      return false;
    }
  }
  
  // success
  return true;
}


/**
 * Format a clump of pattern elements using the specified time.
 */
function _subformat(
  inString,
  localeSymbols,
  formatType,
  startIndex,
  charCount,
  time,
  stringHolder
  )
{    

  // string to append to the toString
  var appendString = null;
  
  var amPMAdjust = false;
    
  if ((formatType >= 'A') && (formatType <= 'Z') ||
      (formatType >= 'a') && (formatType <= 'z'))
  {
    switch (formatType)
    {
      case 'D': // day in year
        appendString = "(Day in Year)";
        break;
      
      case 'E': // day in week
      {
        var dayOfWeek = time.getDay();
        
        appendString = (charCount <= 3)
                         ? localeSymbols.getShortWeekdays()[dayOfWeek]
                         : localeSymbols.getWeekdays()[dayOfWeek];
      }
      break;
      
      case 'F': // day of week in month
        appendString = "(Day of week in month)";
        break;
      
      case 'G': // era designator
      {
        var eras = localeSymbols.getEras();
        
        appendString = (time.getTime() < _getADEra().getTime())
                         ? eras[0]
                         : eras[1];
 
      }
      break;
                  
      case 'M': // month in year
      {
        var monthIndex = time.getMonth();
        
        if (charCount <= 2)
        {
          // use the month number
          appendString = _getPaddedNumber(monthIndex + 1, charCount);
        }
        else if (charCount == 3)
        {
          // use the month abbreviation
          appendString = localeSymbols.getShortMonths()[monthIndex];
        }
        else
        {
          // use the full month name
          appendString = localeSymbols.getMonths()[monthIndex];
        }
      }
      break;
      
      case 'S': // millisecond (0 - 999)
        appendString = _getPaddedNumber(time.getMilliseconds(), charCount);
        break;
      
      case 'W': // week in month
        appendString = "(Week in Month)";
        break;
      
      case 'a': // am/pm marker
      {
        var amPMs = localeSymbols.getAmPmStrings();
        
        appendString =  (_isPM(time.getHours()))
                          ? amPMs[1]
                          : amPMs[0];
      }
      break;
      
      case 'd': // day in month
        appendString = _getPaddedNumber(time.getDate(), charCount);
        break;
      
      case 'h': // hour in am/pm (1-12)
        hours = time.getHours();
        
        if (_isPM(hours))
          hours -= 12;
        
        if (hours == 0)
          hours = 12;

        appendString = _getPaddedNumber(hours, charCount);
        break;
      
      case 'K': // hour in am/pm (0-11)
         hours = time.getHours();
        
        if (_isPM(hours))
           hours -= 12;
         
        appendString = _getPaddedNumber(hours, charCount);
        break;

      case 'k': // hour in day (1-24)
        hours = time.getHours();
        if (hours == 0)
          hours = 24;
        appendString = _getPaddedNumber(hours, charCount);
        break;

      case 'H': // hour in day (0-23)
        appendString = _getPaddedNumber(time.getHours(), charCount);
        break;


      case 'm': // minute in hour 0 - 59)
        appendString = _getPaddedNumber(time.getMinutes(), charCount);
        break;
 
      case 's': // seconds in minute 0 - 59)
        appendString = _getPaddedNumber(time.getSeconds(), charCount);
        break;
   
      case 'w': // week in year
        appendString = "(Week in year)";
        break;
      
      case 'y': // year
      {
        var year = time.getFullYear();
        
        // truncate 2 and 1 digit years to that number of digits
        var maxDigits = (charCount <= 2)
                          ? charCount
                          : null;
                          
                          
        appendString = _getPaddedNumber(year, charCount, maxDigits);
      }
      break;
        
        
      case 'z': // GMT timezone - "GMT Sign Hours : Minutes"
      {
        appendString = "GMT";
        
        var tzString = _getTimeZoneOffsetString(time, false);
        if (tzString)
        {
          // +/-HH:mm
          appendString += tzString[0];
          appendString += ":"
          appendString += tzString[1];
        }
      }
      break;
      
      case 'Z': // RFC 822 timeZone - "Sign TwoDigitHours Minutes"
      { 
        var tzString = _getTimeZoneOffsetString(time, true);
        if (tzString)
        {
          // +/-HHmm
          appendString = tzString[0];
          appendString += tzString[1];
        }
        else
        {
          appendString = "";
        }
      }
      break;
    
      default:
        // do nothing rather than throw an exception
        appendString = "";
    }
  }
  else
  {
    // all other results are literal
    appendString = inString.substring(startIndex, startIndex + charCount);
  }
  
  stringHolder.value += appendString;
  
  // formatting should never fail
  return true;
}

/**
 * Returns the timeZone offset from GMT in the array:
 * [0] = "+/-HH", [1] = "mm".
 */
function _getTimeZoneOffsetString(time, rfcFormat)
{

  // timeZoneOffset in javascript gives
  // the wrong sign, so I am switching it.
  var timeZnOffset = -1* time.getTimezoneOffset();
  timeZnOffset += _getLocaleTimeZoneDifference();
  
  if(rfcFormat || timeZnOffset != 0)
  {
    var timeOffsetString = new Array(2);
    // sign
    if (timeZnOffset < 0)
    {
      timeOffsetString[0] = "-";
      // abs value
      timeZnOffset = -timeZnOffset
    }
    else
    {
      timeOffsetString[0] = "+";
    }
    
    // HH
    timeOffsetString[0] += _getPaddedNumber(Math.floor(timeZnOffset / 60), 2);
    
    // mm
    timeOffsetString[1] = _getPaddedNumber(timeZnOffset % 60, 2);
    
    return timeOffsetString;
  }
}

/**
 * compare the time zone that is on the client with the time zone that
 * came from the localeContext on the server, and return the difference
 * in hours.
 * This can be used to adjust the date/time value that will be displayed in
 * the date field to use the timezone set on the locale context on the 
 * server instead of the timezone we get from javascript's getTimezoneOffset.
 * see bug 3167883
 */
function _getLocaleTimeZoneDifference()
{
  var currentDate = new Date();
  // timeZoneOffset in javascript appears to give
  // the wrong sign, so I am switching it.
  // the timeZoneOffset is in minutes.
  var currentDateTzOffset = currentDate.getTimezoneOffset() * -1;
  var tzOffsetDiff = 0;
  
  return tzOffsetDiff - currentDateTzOffset;
}

/**
 * Parse a substring using a clump of format elements.
 */
function _subparse(
  inString,      // the pattern string, such as "yyMMdd"
  localeSymbols,
  formatType,    // the current format char, such as 'y'
  startIndex,    // index into inString
  charCount,     // the number of chars of type formatType
  parseContext,  // information pertaining to the user input string
  parsedTime
  )
{
  // Start index of the string being parsed (as opposed
  // to startIndex, which is the index on the format mask)
  var inStartIndex = parseContext.currIndex;
    
  var nextFormatType = (startIndex + charCount < inString.length) ? 
    inString.charAt(startIndex + charCount) : null;
    
  // Consider the pattern "yyMMdd". Say that formatType is 'y' and nextFormatType is 'M'. Normally 
  // we would allow for leniency such that the user could input 2 or 4 digits for the year, but 
  // since this pattern contains no date separators and both the year and month can consist of 
  // digits, there's no easy way of telling whether the first 4 digits apply to just the year, or 
  // to both the year and month. Therefore, if nextFormatType is one of the reserved format types, 
  // then we go into strict parsing mode for formatType, where charCount represents the maximum 
  // number of user input characters that will be parsed when matching the current formatType.
  var isStrict = ("DFMSWdhkHKmswy".indexOf(nextFormatType) != -1);

  if ((formatType >= 'A') && (formatType <= 'Z') ||
      (formatType >= 'a') && (formatType <= 'z'))
  {
    switch (formatType)
    {
      case 'D': // day in year
        // skip this number
        if (_accumulateNumber(parseContext, !isStrict ? 3 : charCount) == null)
        {
          return false;
        }
        break;
      
      case 'E': // day in week
      {
        // extract the day but do nothing with it, as there is not setDay()
        // on Date
        var dayIndex = _matchArray(parseContext,
                                   (charCount <= 3)
                                     ? localeSymbols.getShortWeekdays()
                                     : localeSymbols.getWeekdays());
                                     
        if (dayIndex == null)
        {
          return false;
        }
      }
      break;
      
      case 'F': // day of week in month
        // skip this number
        if (_accumulateNumber(parseContext, !isStrict ? 2 : charCount) == null)
        {
          return false;
        }
        break;
      
      case 'G': // era designator
      {
        var eraIndex = _matchArray(parseContext, localeSymbols.getEras());
        
        if (eraIndex != null)
        {
          if (eraIndex == 0)
          {
            parseContext.isBC = true;
          }
        }
        else
        {
          return false;
        }
      }
      break;
                  
      case 'M': // month in year
      {
        var monthIndex;
        var monthOffset = 0;

        if (charCount <= 2)
        {
          // match month number
          monthIndex = _accumulateNumber(parseContext, !isStrict ? 2 : charCount);
          
          // subtract 1 from the monthIndex to make it 0-based
          monthOffset = -1;
        }
        else
        {
          var nameArray = (charCount == 3)
                            ? localeSymbols.getShortMonths()
                            : localeSymbols.getMonths();

          monthIndex = _matchArray(parseContext, nameArray);
        }

        if (monthIndex != null)
        {
          parseContext.parsedMonth = (monthIndex + monthOffset);
        }
        else
        {
          return false;
        }
      }
      break;

      case 'S': // millisecond (0 - 999)
      {
        var milliseconds = _accumulateNumber(parseContext, !isStrict ? 3 : charCount);

        if (milliseconds != null)
        {
          parseContext.parsedMilliseconds = milliseconds;
        }
        else
        {
          return false;
        }
      }
      break;
      
      case 'W': // week in month
        // skip this number
        if (_accumulateNumber(parseContext, !isStrict ? 2 : charCount) == null)
        {
          return false;
        }
        break;
      
      case 'a': // am/pm marker
      {
        var amPMIndex = _matchArray(parseContext,
                                    localeSymbols.getAmPmStrings());
        
        if (amPMIndex == null)
        {
          return false;
        }
        else
        {
          if (amPMIndex == 1)
          {
            parseContext.isPM = true;
          }
        }
      }
      break;
      
      case 'd': // day in month
      {
        var dayOfMonth = _accumulateNumber(parseContext, !isStrict ? 2 : charCount);
                
        if (dayOfMonth != null)
        {
          parseContext.parsedDate = dayOfMonth;
        }
        else
        {
          return false;
        }
      }
      break;
        
      case 'h': // hour in am/pm (1-12)
      case 'k': // hour in day (1-24)
      case 'H': // hour in day (0-23)
      case 'K': // hour in am/pm (0-11)
      {
        var hour = _accumulateNumber(parseContext, !isStrict ? 2 : charCount);
        
        if (hour != null)
        {
          if ((formatType == 'h') && (hour == 12))
            hour = 0;
          if ((formatType == 'k') && (hour == 24))
            hour = 0;

          parseContext.parsedHour = hour;
        }
        else
        {
          return false;
        }
      }
      break;


      case 'm': // minute in hour 0 - 59)
      {
        var minutes = _accumulateNumber(parseContext, !isStrict ? 2 : charCount);
        
        if (minutes != null)
        {
          parseContext.parsedMinutes = minutes;
        }
        else
        {
          return false;
        }
      }
      break;
      
      case 's': // seconds in minute 0 - 59)
      {
        var seconds = _accumulateNumber(parseContext, !isStrict ? 2 : charCount);

        if (seconds != null)
        {
          parseContext.parsedSeconds = seconds;
        }
        else
        {
          return false;
        }
      }
      break;

      case 'w': // week in year
        // skip this number
        if (_accumulateNumber(parseContext, !isStrict ? 2 : charCount) == null)
        {
          return false;
        }
        break;

      case 'y': // year
      {
        var year = _accumulateNumber(parseContext, !isStrict ? 4 : charCount);
        var enteredChars = parseContext.currIndex - inStartIndex;
        // if we have a 2-digit year, add in the default year
        if (year != null)
        {
          if ((enteredChars > 2) &&
              (charCount <= 2) &&
              (year <= 999))
          {
            // Block bonus characters;  if they've specified
            // a two-year mask, and there's more than two characters,
            // there might be a problem.  But allow four digits.
            return false;
          }
          else if ((charCount <= 2) && (year >= 0) && (year <= 100))
          {
            year = _fix2DYear(year);
          }
          else if (charCount == 4)
          {
            // Bug 2169562: For four-digit year formats, reject
            // three-year entries.  Fair enough!
            if (enteredChars == 3)
              return false;    
            if (enteredChars <= 2)
              year = _fix2DYear(year);
          }

          // There is no year "0"
          if (year == 0)
            return false;

          parseContext.parsedFullYear = year;
        }
        else
        {
          return false;
        }
      }
      break;
        
      case 'z': // GMT timezone - "GMT Sign Hours : Minutes"
      {
        // consume the GMT portion
        if (!_matchText(parseContext, "GMT"))
        {
          // GMT is must for timeZone entry.
          return false;
        }
        
        // if we have any more chars then parse the remaining "+HH:mm" string.
        if( (parseContext.parseString.length - parseContext.currIndex) > 0)
        {
          // consume the plus or minus
          if(_matchArray(parseContext, ["-", "+"]) == null)
          {
            return false;
          }
          
          // accumulate the hour offset number
          var hourOffset = _accumulateNumber(parseContext, 2);
          if(hourOffset == null)
          {
            return false;
          }
          parseContext.hourOffset = hourOffset;
          
          // consume the separator between HH and mm
          if (!_matchText(parseContext, ":"))
          {
            return false;
          }
          
          // accumulate minute offset number (should have 2 digits)
          var minOffset;
          if(((parseContext.parseString.length - parseContext.currIndex) < 2) ||
             (minOffset = _accumulateNumber(parseContext, 2)) == null)
          {
            return false;
          }
          parseContext.minOffset = minOffset;
        }
      }
      break;
      
      case 'Z': // RFC 822 timezone - "Sign TwoDigitHours Minutes"
      {
        // RFC 822 TimeZone format should have 5 chars (+/-HHmm)
        if ((parseContext.parseString.length - parseContext.currIndex) < 5)
        {
          return false;
        }
        
        // consume the plus or minus
        if(_matchArray(parseContext, ["-", "+"]) == null)
        {
          return false;
        }
          
        // accumulate the hour offset number
        var hourOffset = _accumulateNumber(parseContext, 2)
        if(hourOffset == null)
        {
          return false;
        }
        parseContext.hourOffset = hourOffset;
        
        // accumulate the minute offset number
        var minOffset = _accumulateNumber(parseContext, 2)
        if(minOffset == null)
        {
          return false;
        }
        parseContext.minOffset = null;
      }
      break;
    
      default:
    }
  }
  else
  {
    // consume constants
    return _matchText(parseContext,
                      inString.substring(startIndex, startIndex + charCount));
  }
  
  // match succeeded
  return true;
}


/**
 * Fix two-digit years.
 */
function _fix2DYear(year)
{
  var defaultCentury;

  if (_df2DYS != null)
  {
    // year               51    01
    // offsetYear       1950  1950
    // defaultCentury   1900  1900
    // year             1951  1901
    // year             1951  2001
    var offsetYear = _df2DYS;
    defaultCentury = offsetYear - (offsetYear % 100);

    year += defaultCentury;
    if (year < offsetYear)
      year += 100;
  }
  else
  {
    var currentYear = new Date().getFullYear();
    defaultCentury = currentYear - (currentYear % 100) - 100;

    year += defaultCentury;
 
    // if the new year is now more than 80 years in the past,
    // then it is actually a date in the future, so add the 100 years
    // back in.  The 80 years rule, matches Java's spec
    if (year + 80 < currentYear)
    {
      year += 100;
    }
  }

  return year;
}


/**
 * Match the current text against an array of possibilities, returning
 * the index of the succesful match, or undefined if no match succeeded.
 */
function _matchArray(
  parseContext,
  matchArray
  )
{
  for (var i = 0; i < matchArray.length; i++)
  {
    if (_matchText(parseContext, matchArray[i]))
    {
      return i;
    }
  }
  
  // no match
  return null;
}


/**
 * Match the specified text in a case insensitive manner,
 * returning true and updating the
 * <code>parseContext</code> if the match succeeded.
 */
function _matchText(
  parseContext,
  text
  )
{
  // if no text to match then match will fail
  if (!text)
    return false;

  // get the length of the text to match
  var textLength  = text.length;

  var currIndex   = parseContext.currIndex;
  var parseString = parseContext.parseString;
  
  // determine whether we have enough of the parseString left to match
  if (textLength > parseString.length - currIndex)
  {
    return false;
  }

  //
  // Convert to lowercase for case insensitive match
  //
  // =-= bts Maybe toLocaleLowerCase would be better, but that would cause
  //         problems if the browser locale were different from the application
  //         locale.
  //  
  var parseText  = parseString.substring(currIndex, currIndex + textLength);
  var parseMatch = parseText.toLowerCase();
  var textMatch  = text.toLowerCase();
  
  if (parseMatch != textMatch)
    return false;
    
  // update the current parseContext
  parseContext.currIndex += textLength;
  
  return true;
}
 

/**
 * Accumlates and returns a number at this location or undefined, if
 * there is no number.
 */
function _accumulateNumber(
  parseContext,
  maxLength
  )
{
  var startIndex  = parseContext.currIndex;
  var currIndex   = startIndex;
  var parseString = parseContext.parseString;
  var parseLength = parseString.length;
  if (parseLength > currIndex + maxLength)
    parseLength = currIndex + maxLength;

  var currValue = 0;

  // gather up all of the digits
  while (currIndex < parseLength)
  {
    var currDigit = parseDigit(parseString.charAt(currIndex));

    if (!isNaN(currDigit))
    {
      // add on the digit and shift over the results
      currValue *= 10;
      currValue += currDigit;

      currIndex++;
    }
    else
    {
      break;
    }
  }

  if (startIndex != currIndex)
  {
    // update the current parseContext
    parseContext.currIndex = currIndex;

    // return the numeric version
    return currValue;
  }
  else
  {
    // no number at this location
    return null;
  }
}


/**
 * Returns true if the hour index is considered PM.
 */
function _isPM(
  hours
  )
{
  return (hours >= 12);
}


/**
 * Pad out a number with leading 0's to meet the minDigits digits or
 * truncate to meet the minDigits.
 */
function _getPaddedNumber(
  number,
  minDigits,
  maxDigits
  )
{  
  var stringNumber = number.toString();
  
  //
  // pad out any number strings that are too short
  //
  if (minDigits != null)
  {    
    var addedDigits = minDigits - stringNumber.length;
  
    while (addedDigits > 0)
    {
      stringNumber = "0" + stringNumber;
      addedDigits--;
    }
  }
  
  //
  // truncate any number strings that are too long
  //
  if (maxDigits != null)
  {
    var extraDigits = stringNumber.length - maxDigits;
    
    if (extraDigits > 0)
    {
      stringNumber = stringNumber.substring(extraDigits,
                                            extraDigits + maxDigits);
    }
  }
  
  return stringNumber;
}


/**
 * External variable for TrDateTimeConverter. Maps locales to lists of 
 * convenience patterns.
 */
var _CONVENIENCE_PATTERNS = null;

/**
 * Construct a TrDateTimeConverter with the specifed date pattern for
 * the specified locale.
 */
function TrDateTimeConverter(
  pattern,  
  locale,
  exampleString,
  type,
  messages
  )
{

  // for debugging
  this._class = "TrDateTimeConverter";
  this._exampleString = exampleString;
  this._type = type;
  this._messages = messages;
  this._offset = null;
  
  // save the Locale elements for the specified locale, or client locale
  // if no locale is specified
  this._localeSymbols = getLocaleSymbols(locale);

  // =-= bts need to change default pattern to match JDK
  if (pattern == null)
    pattern = this._localeSymbols.getShortDatePatternString();

  var patterns = this._initPatterns(pattern, locale);

  // Stash away the patterns for later use.
  this._pattern = patterns;
}

TrDateTimeConverter.prototype = new TrConverter();

TrDateTimeConverter.prototype.getFormatHint = function()
{
	//customized hint
	if(this._messages && this._messages["hint"])
	{
    return TrMessageFactory.createCustomMessage(
      this._messages["hint"],
      ""+this._exampleString);
		
	}
	else
	{
		//no customized hint
		var key = "org.apache.myfaces.trinidad.convert.DateTimeConverter." + this._type + "_HINT";
    return TrMessageFactory.createMessage(
      key,
      ""+this._exampleString);
	}
}

TrDateTimeConverter.prototype.getAsString = function(
  formatTime
  )
{

  //correct Date Time ?
  if(this._offset)
  {
    var min = formatTime.getMinutes();
    formatTime.setMinutes((+min) - parseInt(this._offset));
  }
  var stringHolder = new Object();
  stringHolder.value ="";
  
  var pattern = this._pattern;
  if (typeof pattern != "string")
    pattern = pattern[0];
    
  _doClumping(pattern,
              this._localeSymbols,
              _subformat,
              formatTime,
              stringHolder);

  if(this._offset)
  {
  	var gmtDiff = (((this._offset + formatTime.getTimezoneOffset()) * -1) / 60);
  	if(parseInt(gmtDiff) > 0)
  	{
  		stringHolder.value = stringHolder.value + "+"
  	}
  	stringHolder.value = stringHolder.value + gmtDiff + ":00";
  }
  return stringHolder.value;
}

TrDateTimeConverter.prototype.setDiffInMins = function(
  offset
  )
{ 
  this._offset = offset;
}

TrDateTimeConverter.prototype.getDiffInMins = function()
{
  return this._offset;
}

TrDateTimeConverter.prototype.getLocaleSymbols = function()
{
  return this._localeSymbols;
}


/**
 * Parses a String into a Date using the current object's pattern.  If the
 * parsing fails, undefined will be returned.
 */
TrDateTimeConverter.prototype.getAsObject  = function(
  parseString,
  label
  )
{
  // The following are from the javadoc for DateTimeConverter
  // If the specified String is null, return a null. Otherwise, trim leading and trailing whitespace before proceeding.
  // If the specified String - after trimming - has a zero length, return null.
  if (parseString == null)
    return null;

  parseString = TrUIUtils.trim(parseString);
  if (parseString.length == 0)
    return null;

  var pattern = this._pattern;
  
  var facesMessage;
  var key = "org.apache.myfaces.trinidad.convert.DateTimeConverter.CONVERT_"+this._type;
  if(this._messages && this._messages["detail"])
  {
    facesMessage = _createCustomFacesMessage(TrMessageFactory.getSummaryString(key),
                                          this._messages["detail"],
                                          label,
                                          parseString,
                                          this._exampleString);
  }
  else
  {
    facesMessage = _createFacesMessage( key,
                                          label,
                                          parseString,
                                          this._exampleString);
  }
  if (typeof pattern == "string")
  {
    return this._simpleDateParseImpl(parseString,
                                pattern,
                                this._localeSymbols,
                                facesMessage);
  }
  else
  { 
    var i;
    for (i = 0; i < pattern.length; i++)
    {
      try{
        var date = this._simpleDateParseImpl(parseString,
                                        pattern[i],
                                        this._localeSymbols,
                                        facesMessage);
        return date;
      }
      catch (e)
      {
        // if we're not on the last pattern try the next one, 
        // but if we're on the last pattern, throw the exception
        if ( i == pattern.length-1 )
          throw e;
      }
    }
  }
}

TrDateTimeConverter.prototype._endsWith = function(
  value,
  suffix
  )
{
  // TODO: add to a String utils class ?
  var startPos = value.length - suffix.length;
  if (startPos < 0)
    return false;
  return (value.lastIndexOf(suffix, startPos) == startPos);
}

TrDateTimeConverter.prototype._initPatterns  = function(
  pattern, locale)
{
  // We need to build up an Array of all acceptable patterns,
  // which we'll stash away for later use.  If we do lenient
  // parsing, then we may end up supporting a variety of patterns
  // that weren't passed in via the "pattern" arg.  Previously,
  // if the "pattern" arg is itself an Array, we just tacked
  // any additional lentient patterns right into the "pattern"
  // Array.  However, the "pattern" Array is actually referenced
  // from other locations, so we should avoid modifying this
  // array directly.  Instead, we create our own "patterns"
  // Array and append all supported patterns into this Array.
  var patterns = new Array();

  // Array from which the patterns array will be constructed.
  var tmpPatterns = new Array();
  
  // If pattern is non-null, append it to the tmpPatterns array.
  if (pattern)
    tmpPatterns = tmpPatterns.concat(pattern);

  // At this point 'locale' is the value of the locale attribute; if 'locale' is 
  // null, we should make sure to grab the same locale that was grabbed by getLocaleSymbols() (i.e.,getJavaLanguage)
  if (!locale)
    locale = getJavaLanguage(locale);
  
  // Make sure the static map of convenience patterns has been initialized.
  if (!_CONVENIENCE_PATTERNS)
    this._initConveniencePatterns();
  
  // see TRINIDAD-859
  var convPatterns = _CONVENIENCE_PATTERNS[locale];
  if (convPatterns)
    tmpPatterns = tmpPatterns.concat(convPatterns);
  
  // Add the tmp patterns and all their lenient pattern variants.
  var len = tmpPatterns.length;
  for (var c = 0; c < len; c++)
  {
    var convPattern = tmpPatterns[c];
    patterns[patterns.length] = convPattern;
    var baseCount = 1;
    
    // Bug 2002065: 
    // Be forgiving of users who prefer a different separator and alternative 
    // month styles. We are to be lenient by default with ADF Faces.
    
    // We should add all the leniency patterns for this default pattern first.
    // First add in replacements for month parsing.
    if (convPattern.indexOf('MMM') != -1)
    {
      patterns[patterns.length] = convPattern.replace(/MMM/g, 'MM');
      patterns[patterns.length] = convPattern.replace(/MMM/g, 'M');
      baseCount = 3;
    }
    
    // Now add support for all of the above with any of the separators below. 
    // The separator is the same for all patterns since we only replaced month.
    var idx = patterns.length - baseCount;
    if (convPattern.indexOf('/') !=  - 1)
    {
      for (var i = idx; i < idx + baseCount; i++)
        patterns[patterns.length] = patterns[i].replace(/\//g, '-');
      
      for (var i = idx; i < idx + baseCount; i++)
        patterns[patterns.length] = patterns[i].replace(/\//g, '.');
    }
    else if (convPattern.indexOf('-') !=  - 1)
    {
      for (var i = idx; i < idx + baseCount; i++)
        patterns[patterns.length] = patterns[i].replace(/-/g, '/');
      
      for (var i = idx; i < idx + baseCount; i++)
        patterns[patterns.length] = patterns[i].replace(/-/g, '.');
    }
    else if (convPattern.indexOf('.') !=  - 1)
    {
      for (var i = idx; i < idx + baseCount; i++)
        patterns[patterns.length] = patterns[i].replace(/\./g, '-');
      
      for (var i = idx; i < idx + baseCount; i++)
        patterns[patterns.length] = patterns[i].replace(/\./g, '/');
    }
  }
  
  return patterns;
}

/**
 * Initialize the static map of convenience patterns. This should only be called 
 * if _CONVENIENCE_PATTERNS is null (so that this map is recreated only when the 
 * page is reloaded). All map entries MUST match those of the server map:
 * trinidad-api\src\main\java\org\apache\myfaces\trinidad\convert\DateTimeConverter.java->_CONVENIENCE_PATTERNS
 */
TrDateTimeConverter.prototype._initConveniencePatterns = function() 
{
  _CONVENIENCE_PATTERNS = new Object();
  
  // All map entries added here MUST match the entries added to the server map:
  // trinidad-api\src\main\java\org\apache\myfaces\trinidad\convert\DateTimeConverter.java->_CONVENIENCE_PATTERNS
  _CONVENIENCE_PATTERNS.en_US = ["MMMM dd, yy", "MMMM/dd/yy", "dd-MMMM-yy"];  
}

TrDateTimeConverter.prototype._simpleDateParseImpl = function(
  parseString,
  parsePattern,
  localeSymbols,
  msg)
{
  // When a pattern (e.g. dd.MM.yyyy HH:mm' Uhr ') requires a whitespace
  // at the end, we should honor that. As the JSF spec (see http://bit.ly/kTelf)
  // wants the converter to trim leading/trailing whitespace, we have to append
  // one, if the pattern requires it at the end...
  if(this._endsWith(parsePattern, " '"))
  {
    parseString += " ";
  }
	
  var parseContext = new Object();
  parseContext.currIndex = 0;
  parseContext.parseString = parseString;
  parseContext.parsedHour = null;
  parseContext.parsedMinutes = null;
  parseContext.parsedSeconds = null;
  parseContext.parsedMilliseconds = null;
  parseContext.isPM = false;
  parseContext.parsedBC = false;
  parseContext.parsedFullYear = null;
  parseContext.parsedMonth = null;
  parseContext.parsedDate = null;
  parseContext.hourOffset = null;
  parseContext.minOffset = null;
  parseContext.parseException = new TrConverterException( msg);

  var parsedTime = new Date(0);
  parsedTime.setDate(1);

  // parse the time
  if (_doClumping(parsePattern,
                  localeSymbols,
                  _subparse,
                  parseContext,
                  parsedTime))
  {
    if (parseString.length != parseContext.currIndex)
    {
      throw parseContext.parseException;
    }

    // give up instantly if we encounter timezone because
    // the client can never correctly convert to a milliseconds
    // value accurately due to lack of timezone and Daylight savings
    // rules in Javascript
    // Undefined is used in _multiValidate as a flag to skip
    // validation and avoid required errors (which returning null would trigger)
    if ((parseContext.hourOffset != null) || 
       (parseContext.minOffset != null))
      return undefined;

    // Set the parsed year, if any;  adjust for AD vs. BC
    var year = parseContext.parsedFullYear;
    if (year != null)
    {
      // convert year to BC
      if (parseContext.parsedBC)
      {
        year = _getADEra().getFullYear() - year;
      }

      parsedTime.setFullYear(year);
      parseContext.parsedFullYear = year;
    }

    // Set the parsed month, if any
    var month = parseContext.parsedMonth;
    if (month != null)
      parsedTime.setMonth(month);

    // Set the parsed day-of-month, if any
    var date = parseContext.parsedDate;
    if (date != null)
      parsedTime.setDate(date);

    // Set the parsed hour, if any.  Adjust for AM vs. PM
    var hour = parseContext.parsedHour;
    if (hour != null)
    {
      if (parseContext.isPM && (hour < 12))
      {
        hour += 12;
      }

      parsedTime.setHours(hour);
      parseContext.parsedHour = hour;
    }

    // Set the parsed minutes, if any
    var minutes = parseContext.parsedMinutes;
    if (minutes != null)
      parsedTime.setMinutes(minutes);

    // Set the parsed seconds, if any
    var seconds = parseContext.parsedSeconds;
    if (seconds != null)
      parsedTime.setSeconds(seconds);

    // Set the parsed milliseconds, if any
    var milliseconds = parseContext.parsedMilliseconds;
    if (milliseconds != null)
      parsedTime.setMilliseconds(milliseconds);

    // so far we have done a lenient parse
    // now we check for strictness
    if (!_isStrict(parseContext, parsedTime))
    {
      throw parseContext.parseException;
    }
      
    //correct Date Time ?
    if(this._offset)
    {
      var min = parsedTime.getMinutes();
      parsedTime.setMinutes((+min) + parseInt(this._offset));
    }

    return parsedTime;
  }
  else
  {
    // failure
    throw parseContext.parseException;
  }
}

/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 * 
 *  http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */

var _digits;
var _decimalSep;
var _groupingSep;

/**
 * Returns true if the character is a digit.
 */
function isDigit(
  digitChar
  )
{  
  return (_getDigits()[digitChar] != null);
}


/**
 * Returns an Object containing the digit value for all numeric characters
 * and undefined for non-numeric characters
 */
function _getDigits()
{
  if (_digits == null)
  {
    // starts of 10 digit unicode ranges
    var digitStarts = [
                        0x0030, // ISO-LATIN-1 digits ('0' through '9')
                        0x0660, // Arabic-Indic digits
                        0x06F0, // Extended Arabic-Indic digits
                        0x0966, // Devanagari digits
                        0x09E6, // Bengali digits
                        0x0A66, // Gurmukhi digits
                        0x0AE6, // Gujarati digits
                        0x0B66, // Oriya digits
                        0x0BE7, // Tamil digits
                        0x0C66, // Telugu digits
                        0x0CE6, // Kannada digits
                        0x0D66, // Malayalam digits
                        0x0E50, // Thai digits
                        0x0ED0, // Lao digits
                        0x0F20, // Tibetan digits
                        0xFF10  // Fullwidth digits
                      ];
    
    _digits = new Object();
    
    for (var i = 0; i < digitStarts.length; i++)
    {
      for (var offset = 0; offset < 10; offset++)
      {
        // get the string value of the current unicode character
        var currKey = String.fromCharCode(digitStarts[i] + offset);
        
        // store the digit value of this character
        _digits[currKey] = offset;
      }
    }
  }
  
  return _digits;
}


/**
 * Returns thenumeric value of a digit character or Nan if the
 * character isn't a digit.
 */
function parseDigit(
  digitChar
  )
{  
  var value = _getDigits()[digitChar];
  
  if (value == null)
  {
    return NaN;
  }
  else
  {
    return value;
  }
}


/**
 * Returns true if a character isn't a lowercase character or
 * might not be a lowercase character.
 */
function isNotLowerCase()
{
  var charCode = alphaChar.charCodeAt(0);

  if (charCode > 0xFF)
  {
    // be lenient for non-ISO-Latin1
    return true;
  }
  else
  {    
    return !_isLowerCaseStrict(alphaChar);
  }
}


/**
 * Returns true if a character is a lowercase character or
 * might be a lowercase character.
 */
function isLowerCase(
  alphaChar
  )
{
  var charCode = alphaChar.charCodeAt(0);

  if (charCode > 0xFF)
  {
    // be lenient for non-ISO-Latin1
    return !isDigit(alphaChar);
  }
  else
  {    
    return _isLowerCaseStrict(alphaChar);
  }
}


function _isLowerCaseStrict(
  alphaChar
  )
{
  var charCode = alphaChar.charCodeAt(0);

  return (((charCode >= 0x61) && (charCode <= 0x7A)) || // "a-z"
          ((charCode >= 0xDF) && (charCode <= 0xFF)));  // iso-latin1 lowercase
}


/**
 * Returns true if a character is an uppercase character or might be an
 * uppercase character.
 */
function isUpperCase(
  alphaChar
  )
{
  var charCode = alphaChar.charCodeAt(0);
  
  if (charCode > 0xFF)
  {
    // be lenient for non-IS-Latin1
    return !isDigit(alphaChar);
  }
  else
  {
    return _isUpperCaseStrict(alphaChar);
  }
}


/**
 * Returns true if a character isn't an uppercase character or might not be an
 * uppercase character.
 */
function isNotUpperCase(
  alphaChar
  )
{
  var charCode = alphaChar.charCodeAt(0);
  
  if (charCode > 0xFF)
  {
    // be lenient for non-IS-Latin1
    return true;
  }
  else
  {
    return !_isUpperCaseStrict(alphaChar);
  }
}


/**
 * Returns true if a character is an uppercase character.
 */
function _isUpperCaseStrict(
  alphaChar
  )
{
  var charCode = alphaChar.charCodeAt(0);
  
  return (((charCode >= 0x41) && (charCode <= 0x5A)) || // "A-Z"
          ((charCode >= 0xC0) && (charCode <= 0xDe)));  // iso-latin1 lowercase
}


/**
 * Returns true if a character is a latter.
 */
function isLetter(
  alphaChar
  )
{
  // =-= bts not technically correct but hopefully OK for ISO-Latin1
  return isLowerCase(alphaChar) | isUpperCase(alphaChar);
}


function getUserLanguage()
{
  var language = _locale;

  if (language == null)
  {
    // try this the IE way
    language =  window.navigator.userLanguage;
    
    if (language == null)
    {
      // try this the Netscape way
      language = window.navigator.language;
    }
  }
  
  // return language;
  return language;
}


function getJavaLanguage(
  javascriptLang
  )
{
  // default to the user language if no language is passed in
  if (javascriptLang == null)
  {
    javascriptLang = getUserLanguage();
  }
      
  // look for first dash, the territory appears after the dash
  var territoryIndex = javascriptLang.indexOf("-", 0);
  
  // no dash found, so the name is just a language;
  if (territoryIndex == -1)
    return javascriptLang;
  
  var inLength = javascriptLang.length;
  var javaLang = javascriptLang.substring(0, territoryIndex);
  
  javaLang += "_";
  
  territoryIndex++;
  
  var variantIndex = javascriptLang.indexOf("-", territoryIndex);
  
  if (variantIndex == -1)
  {
    // we have no variant
    variantIndex = inLength;
  }
  
  var territoryString = javascriptLang.substring(territoryIndex,
                                                 variantIndex);
                                                 
  javaLang += territoryString.toUpperCase();
  
  // we have a variant, so add it
  if (variantIndex != inLength)
  {
    javaLang += "_";
    javaLang += javascriptLang.substring(variantIndex + 1,
                                         inLength);
  }
    
  return javaLang;
}
 

function getLocaleSymbols(
  jsLocaleString
  )
{  
  var suffix = getJavaLanguage(jsLocaleString);
    
  //
  // look for our localeSymbols, from most specific to least
  // specific.  Unfortunately, this will only work if the
  // less specific library has already been loaded.
  //
  while(true)
  {    
    var localeSymbols = window["LocaleSymbols_" + suffix];
    
    if (localeSymbols != null)
    {
      return localeSymbols;
    }
    else
    {
      var previousIndex = suffix.lastIndexOf("_");
      
      if (previousIndex != -1)
      {
        suffix = suffix.substring(0, previousIndex);
      }
      else
      {
        break;
      }
    }
  }
}


function _getEras()
{
  return this.getLocaleElements()["Eras"];
}

function _getMonths()
{
  return this.getLocaleElements()["MonthNames"];
}

function _getShortMonths()
{
  return this.getLocaleElements()["MonthAbbreviations"];
}

function _getWeekdays()
{
  return this.getLocaleElements()["DayNames"];
}

function _getShortWeekdays()
{
  return this.getLocaleElements()["DayAbbreviations"];
}

function _getAmPmStrings()
{
  return this.getLocaleElements()["AmPmMarkers"];
}

function _getZoneStrings()
{
  return this.getLocaleElements()["zoneStrings"];
}

function _getLocalPatternChars()
{
  return this.getLocaleElements()["localPatternChars"];
}


function _getDecimalSeparator()
{
  if (_decimalSep != null)
    return _decimalSep;

  return this.getLocaleElements()["NumberElements"][0];
}

function _getGroupingSeparator()
{
  if (_groupingSep != null)
    return _groupingSep;

  return this.getLocaleElements()["NumberElements"][1];
}

function _getPatternSeparator()
{
  return this.getLocaleElements()["NumberElements"][2];
}

function _getPercent()
{
  return this.getLocaleElements()["NumberElements"][3];
}

function _getPercentSuffix()
{
  return this.getLocaleElements()["PercentElements"][0];
}

function _getZeroDigit()
{
  return this.getLocaleElements()["NumberElements"][4];
}

function _getDigit()
{
  return this.getLocaleElements()["NumberElements"][5];
}

function _getMinusSign()
{
  return this.getLocaleElements()["NumberElements"][6];
}

function _getExponential()
{
  return this.getLocaleElements()["NumberElements"][7];
}

function _getPerMill()
{
  return this.getLocaleElements()["NumberElements"][8];
}

function _getInfinity()
{
  return this.getLocaleElements()["NumberElements"][9];
}

function _getNaN()
{
  return this.getLocaleElements()["NumberElements"][10];
}

function _getCurrencySymbol()
{
  return this.getLocaleElements()["CurrencyElements"][0];
}
function _getCurrencyCode()
{
  return this.getLocaleElements()["CurrencyElements"][1];
}
function _getPositivePrefix()
{
  return this.getLocaleElements()["CurrencyElements"][2];
}
function _getPositiveSuffix()
{
  return this.getLocaleElements()["CurrencyElements"][3];
}
function _getNegativePrefix()
{
  return this.getLocaleElements()["CurrencyElements"][4];
}
function _getNegativeSuffix()
{
  return this.getLocaleElements()["CurrencyElements"][5];
}

function _getLocaleElements()
{
  return this["LocaleElements"];
}

function _getFullTimePatternString()
{
  return this.getLocaleElements()["DateTimePatterns"][0];
}

function _getLongTimePatternString()
{
  return this.getLocaleElements()["DateTimePatterns"][1];
}

function _getMediumTimePatternString()
{
  return this.getLocaleElements()["DateTimePatterns"][2];
}

function _getShortTimePatternString()
{
  return this.getLocaleElements()["DateTimePatterns"][3];
}

function _getFullDatePatternString()
{
  return this.getLocaleElements()["DateTimePatterns"][4];
}

function _getLongDatePatternString()
{
  return this.getLocaleElements()["DateTimePatterns"][5];
}

function _getMediumDatePatternString()
{
  return this.getLocaleElements()["DateTimePatterns"][6];
}

function _getShortDatePatternString()
{
  return this.getLocaleElements()["DateTimePatterns"][7];
}

function _getDateTimeFormatString()
{
  return this.getLocaleElements()["DateTimePatterns"][8];
}


function LocaleSymbols(
  localeElements
  )
{
  this["LocaleElements"] = localeElements;
}

LocaleSymbols.prototype.getFullTimePatternString = _getFullTimePatternString;
LocaleSymbols.prototype.getLongTimePatternString = _getLongTimePatternString;
LocaleSymbols.prototype.getMediumTimePatternString = _getMediumTimePatternString;
LocaleSymbols.prototype.getShortTimePatternString = _getShortTimePatternString;
LocaleSymbols.prototype.getFullDatePatternString = _getFullDatePatternString;
LocaleSymbols.prototype.getLongDatePatternString = _getLongDatePatternString;
LocaleSymbols.prototype.getMediumDatePatternString = _getMediumDatePatternString;
LocaleSymbols.prototype.getShortDatePatternString = _getShortDatePatternString;
LocaleSymbols.prototype.getDateTimeFormatString = _getDateTimeFormatString;

LocaleSymbols.prototype.getEras = _getEras;
LocaleSymbols.prototype.getMonths = _getMonths;
LocaleSymbols.prototype.getShortMonths = _getShortMonths;
LocaleSymbols.prototype.getWeekdays = _getWeekdays;
LocaleSymbols.prototype.getShortWeekdays = _getShortWeekdays;
LocaleSymbols.prototype.getAmPmStrings = _getAmPmStrings;
LocaleSymbols.prototype.getZoneStrings = _getZoneStrings;
LocaleSymbols.prototype.getLocalPatternChars = _getLocalPatternChars;

LocaleSymbols.prototype.getDecimalSeparator = _getDecimalSeparator;
LocaleSymbols.prototype.getGroupingSeparator = _getGroupingSeparator;
LocaleSymbols.prototype.getPatternSeparator = _getPatternSeparator;
LocaleSymbols.prototype.getPercent = _getPercent;
LocaleSymbols.prototype.getPercentSuffix = _getPercentSuffix;
LocaleSymbols.prototype.getZeroDigit = _getZeroDigit;
LocaleSymbols.prototype.getDigit = _getDigit;
LocaleSymbols.prototype.getMinusSign = _getMinusSign;
LocaleSymbols.prototype.getExponential = _getExponential;
LocaleSymbols.prototype.getPerMill = _getPerMill;
LocaleSymbols.prototype.getInfinity = _getInfinity;
LocaleSymbols.prototype.getNaN = _getNaN;
LocaleSymbols.prototype.getCurrencySymbol = _getCurrencySymbol;
LocaleSymbols.prototype.getCurrencyCode   = _getCurrencyCode;
LocaleSymbols.prototype.getPositivePrefix = _getPositivePrefix;
LocaleSymbols.prototype.getPositiveSuffix = _getPositiveSuffix;
LocaleSymbols.prototype.getNegativePrefix = _getNegativePrefix;
LocaleSymbols.prototype.getNegativeSuffix = _getNegativeSuffix;
LocaleSymbols.prototype.getLocaleElements = _getLocaleElements;

/**
 * ConverterHint "interface" for a client side TrConverter instance.
 * The ConverterHint "interface" is for guiding a user on the desired format to ensure
 * that no converter exceptions are thrown.
 *
 */
function TrConverterHint()
{
  // for debugging
  this._class = "TrConverterHint";
}

/**
 * Returns a hint for the used converter, which format is
 * expected by the ui component.
 */
TrConverterHint.prototype.getFormatHint = function(){}

/**
 * ValidatorHint "interface" for a client side TrValidator instance.
 * The ValidatorHint "interface" is to guide a user when entering a
 * value to an input component, to ensure that no validator exceptions is thrown.
 */
function TrValidatorHint()
{
  // for debugging
  this._class = "TrValidatorHint";
}

/**
 * Since an implementation of this "interface" can have multiple
 * hints available, we return all available hint messages in an JavaScript Array.
 * 
 * @param converter converter is passed to this method, because sometimes a default converter is used
 * and the validator, implementing this interface, shouldn't need to figure out
 * anything about that
 */
TrConverterHint.prototype.getHints = function(converter){}


/**
 * Converter "interface" similar to javax.faces.convert.Converter,
 * except that all relevant information must be passed to the constructor
 * as the context and component are not passed to the getAsString or getAsObject method 
 *
 */
function TrConverter()
{
  // for debugging
  this._class = "TrConverter";
}

/**
 * Convert the specified model object value, into a String for display
 *
 * @param value Model object value to be converted 
 * @param label label to identify the editableValueHolder to the user 
 * 
 * @return the value as a string or undefined in case of no converter mechanism is
 * available (see TrNumberConverter).
 */
TrConverter.prototype.getAsString = function(value, label){}

/**
 * Convert the specified string value into a model data object 
 * which can be passed to validators
 *
 * @param value String value to be converted 
 * @param label label to identify the editableValueHolder to the user 
 * 
 * @return the converted value or undefined in case of no converter mechanism is
 * available (see TrNumberConverter).
 */
TrConverter.prototype.getAsObject = function(value, label){}

/**
 * Validator "interface" similar to javax.faces.validator.Validator,
 * except that all relevant information must be passed to the constructor
 * as the context and component are not passed to the validate method 
 *
 */
function TrValidator()
{
  // for debugging
  this._class = "TrValidator";
}

/**
 * Perform the correctness checks implemented by this Validator. 
 * If any violations are found, a TrValidatorException will be thrown 
 * containing the TrFacesMessage describing the failure. 
 * @param value value to be validated 
 * @param label label to identify the editableValueHolder to the user
 * @param converter converter to format error string properly
 */
TrValidator.prototype.validate = function(value, label, converter){}


/** 
 * TrConverterException is an exception thrown by the getAsObject() or getAsString() 
 * method of a TrConverter, to indicate that the requested conversion cannot be performed.
 *
 * @param facesMessage the TrFacesMessage associated with this exception
 * @param summary Localized summary message text, used only if facesMessage is null
 * @param detail Localized detail message text, used only if facesMessage is null
 */
function TrConverterException(
  facesMessage, 
  summary,
  detail
  )
{
  
  if (facesMessage == null)
  {
      this._facesMessage = new TrFacesMessage(summary, 
                                            detail,
                                            TrFacesMessage.SEVERITY_ERROR);
  }
  else
  {
    this._facesMessage = facesMessage;
  }      
    
  
}

/**
 * Returns the TrFacesMessage associated with the exception.
 */
TrConverterException.prototype.getFacesMessage = 
    function()
    {
      return this._facesMessage;
    }



/**
 * A TrValidatorException is an exception thrown by the validate() method of 
 * a Validator to indicate that validation failed.
 *
 * @param facesMessage the TrFacesMessage associated with this exception
 * @param summary Localized summary message text, used only if facesMessage is null
 * @param detail Localized detail message text, used only if facesMessage is null
 */
function TrValidatorException(
  facesMessage,
  summary, 
  detail
  )
{
  
  if (facesMessage == null)
  {
      this._facesMessage = new TrFacesMessage(summary, 
                                            detail,
                                            TrFacesMessage.SEVERITY_ERROR);
  }
  else
  {
    this._facesMessage = facesMessage;
  }      
}


/**
 * Returns the TrFacesMessage associated with the exception.
 */
TrValidatorException.prototype.getFacesMessage = 
  function()
  {
    return this._facesMessage;
  }

/**
 * Message similar to javax.faces.application.FacesMessage
 *
 * @param summary - Localized summary message text
 * @param detail - Localized detail message text 
 * @param severity - An optional severity for this message.  Use constants
 *                   SEVERITY_INFO, SEVERITY_WARN, SEVERITY_ERROR, and
 *                   SEVERITY_FATAL from the FacesMessage class.  Default is
 *                   SEVERITY_INFO
 */
function TrFacesMessage(
  summary,
  detail,
  severity
  )
{
  this._summary = summary;
  this._detail = detail;
  
  if(severity == null)
  {
    this._severity = TrFacesMessage.SEVERITY_INFO;
  }
  else
  {
    this._severity = severity;
  }
}

TrFacesMessage.SEVERITY_INFO    = 0;
TrFacesMessage.SEVERITY_WARN    = 1;
TrFacesMessage.SEVERITY_ERROR   = 2;
TrFacesMessage.SEVERITY_FATAL   = 3;

TrFacesMessage._SEVERITY_DEFAULT = TrFacesMessage.SEVERITY_INFO;
 
TrFacesMessage.prototype.getDetail = 
  function()
  {
    return this._detail;
  }
TrFacesMessage.prototype.getSummary = 
  function()
  {
    return this._summary;
  }
TrFacesMessage.prototype.setDetail = 
  function(
    detail
    )
  {
    this._detail = detail;
  }
TrFacesMessage.prototype.setSummary = 
  function(
    summary
    )
  {
    this._summary = summary;
  }

TrFacesMessage.prototype.getSeverity =
  function()
  {
    return this._severity;
  }
    
TrFacesMessage.prototype.setSeverity =
  function(
    severity
  )
  {
    this._severity = severity;
  }


/**
 * TrFastMessageFormatUtils is a greatly reduced version
 * of the java.text.MessageFormat class, but delivered as a utility. 
 * <p>
 * The only syntax supported by this class is simple index-based
 * replacement, namely:
 * <pre>
 *     some{1}text{0}here{2}andthere
 * </pre>
 * Unlike MessageFormat, single quotes are NOT used for escaping.  
 * So, the following pattern could be used to include a left bracket:
 * <pre>
 *     some{text{0}
 * </pre>
 */
var TrFastMessageFormatUtils = new Object();

 /**
  * This formatter will only replace patterns of the type "{[0-9]}" 
  * for which there is an associated token.
  * Any other use of '{}' will be interpreted as literal text.
  * This aims to have the same behavior as FastMessageFormat.format 
  * on the server.
  * @param {String} String to format
  * @param {any...:undefined} Varargs objects to substitute for positional parameters.
  * Each parameter will be converted to a String and substituted into the format.
  */
TrFastMessageFormatUtils.format = function(
  formatString, // error format string with embedded indexes to be replaced
  parameters    // {any...:undefined} Varargs objects to substitute for positional parameters.
  )
{
  // There are arguments.length - 1 tokens:
  // arguments[1], ..., arguments[arguments.length-1]
  var formatLength = formatString.length;
  var tokenCount = arguments.length - 1;
  
  // Use the javascript StringBuffer technique.
  var buffer = [];

  var lastStart = 0;
  for (var i = 0; i < formatLength; i++)
  {
    // IE7 does not support the string[index] syntax, so use string.charAt(index) instead.
    var ch = formatString.charAt(i);
    if (ch == '{')
    {
      // Only check for single digit patterns that have an associated token.
      if (i + 2 < formatLength && formatString.charAt(i+2) == '}')
      {
        var tokenIndex = formatString.charAt(i+1) - '0';
        if (tokenIndex >= 0 && tokenIndex < tokenCount)
        {            
          // Use the javascript StringBuffer technique for append(string)
          var substr = formatString.substring(lastStart, i);
          buffer.push(substr);
          
          var token = arguments[tokenIndex+1];
          if (token != null)
            buffer.push(token);

          i += 2;
          lastStart = i + 1;
        }
      }
    }
    // ELSE: Do nothing. The character will be added in later.
  }
  
  if (lastStart < formatLength)
  {
    var substr = formatString.substring(lastStart);
    buffer.push(substr);
  }

  // Use the javascript StringBuffer technique for toString()
  return buffer.join("");
}

var TrMessageFactory = new Object();

TrMessageFactory.createFacesMessage = function(
  key,
  customDetail,
  parameters,
  messageSeverity
  )
{  
  // the strings to create a facesMessage to use have been sent down
  var summary = TrMessageFactory.getSummaryString(key);       
  var detail = customDetail;
  var severity = messageSeverity;
  
  if ( severity == null)
  {
    severity = TrFacesMessage.SEVERITY_ERROR
  }
  
  if (detail == null)
  {
    detail =  TrMessageFactory.getDetailString(key); 
  }
  
  if ( detail != null )
  {
    if ( parameters != null )
    {
      detail = TrFastMessageFormatUtils.format(detail,parameters);
    }
  }
    
  return new TrFacesMessage( summary, detail, severity);
}

TrMessageFactory.getSummaryString = function(
  key
  )
{
  if (key == null)
    return null;
  return TrMessageFactory._TRANSLATIONS[key];
}

TrMessageFactory.getDetailString = function(
  key
  )
{
  if (key == null)
    return null;
    
  // TODO should I be doing string concat here, or have a map of key -> detailKey?
  return TrMessageFactory._TRANSLATIONS[key+"_detail"];
}

TrMessageFactory.getString = function(
  key
  )
{
  return TrMessageFactory.getSummaryString(key);
}

TrMessageFactory.createMessage = function(
  key,
  param1,
  param2
  )
{  
  // the strings to create a facesMessage to use have been sent down
  var message = TrMessageFactory.getSummaryString(key);       
  if ( message != null )
  {
    message = TrFastMessageFormatUtils.format(message, param1, param2);
  }
    
  return message;
}
TrMessageFactory.createCustomMessage = function(
  customMessage,
  param1,
  param2
  )
{
  // the strings to create a facesMessage to use have been sent down
  var message;
  if ( customMessage != null )
  {
    message = TrFastMessageFormatUtils.format(customMessage, param1, param2);
  }
  return message;
}

/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 * 
 *  http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */
 
// Called by the renderer to register the message box 
// and create an associated TrMessageBox instance
TrMessageBox._registerMessageBox = function(messageBoxId)
{
  if (!TrMessageBox._MESSAGE_BOX)
    TrMessageBox._MESSAGE_BOX = new TrMessageBox(messageBoxId);
}

/**
 * Adds a message to the message box for a given input.  The
 * label is used as the anchor text for rendering a link to
 * the given input.  Also shows the message box if it was previously
 * empty.
 **/
TrMessageBox.addMessage = function(inputId, label, facesMessage)
{
  var messageBox = TrMessageBox._MESSAGE_BOX;
  if (!messageBox)
    return;

  messageBox.addMessage(inputId, label, facesMessage);
}

/**
 * Removes any messages that are shown for the given input.
 * Also hides the message box if there are no.
 **/
TrMessageBox.removeMessages = function(inputId)
{
  var messageBox = TrMessageBox._MESSAGE_BOX;
  if (!messageBox)
    return;

  messageBox.removeMessages(inputId);
}

/**
 * Checks if a tr:messages component is present on-screen.
 **/
TrMessageBox.isPresent = function()
{
  return (TrMessageBox._MESSAGE_BOX) ? true : false;
}

function TrMessageBox(messageBoxId)
{
  if (messageBoxId == undefined)
    return;

  //define object properties
  this._messageBoxId = messageBoxId;
  
  
  // Change 'link' this once skin style is made public
  TrMessageBox._LINK_STYLE = TrPage.getInstance().getStyleClass("OraLink");
  TrMessageBox._LIST_STYLE = TrPage.getInstance().getStyleClass("af|messages::list");
  TrMessageBox._LIST_SINGLE_STYLE = TrPage.getInstance().getStyleClass("af|messages::list-single");
}

/**
 * Adds a message to the MessageBox and makes it visible if hidden.
 **/
TrMessageBox.prototype.addMessage = function(inputId, label, facesMessage)
{
  var listElement = this._getMessageList();
  
  // Create a new line element
  var line = document.createElement("li");
  
  if (inputId)
  {
    // Use summary text if label not available
    if (!label)
      label = facesMessage.getSummary();    

    var textNode;
    // Check that the label actually contains text
    if (label && label.length > 0)
    {
      // Create a clickable anchor
      var anchor = document.createElement("a");
      anchor.className = TrMessageBox._LINK_STYLE;
      anchor.href = "#" + inputId;
      anchor.innerHTML = label;
      line.appendChild(anchor);
   
      // Populate the text on the line
      textNode = document.createTextNode(" - " + facesMessage.getSummary());
    }
    else
    {
      // don't append the " - "
      textNode = document.createTextNode(facesMessage.getSummary());
    }
    
    // Give it a name we can remember so we can remove it later
    line.name = this._getMessageNameForInput(inputId);
    line.appendChild(textNode);
  }
  else
  {
    // Treat message as global message, which can't be individually removed

    // Populate the text on the line
    var summary = facesMessage.getSummary();   
    var textNode;
    
    if (summary && summary.length > 0)
      textNode = document.createTextNode(summary + " - " + facesMessage.getDetail());
    else
      textNode = document.createTextNode(facesMessage.getDetail());
      
    line.appendChild(textNode);
  }

  // Add line element to the list
  listElement.appendChild(line);
  
  if (listElement.hasChildNodes())
  {
    var children = listElement.getElementsByTagName("li");
    
    if (children.length == 1)
      // Set the style for a list of only 1 entry
      listElement.className = TrMessageBox._LIST_SINGLE_STYLE;
    else
      // Set the style for a multi-entry list
      listElement.className = TrMessageBox._LIST_STYLE;
  }
  
  this._showMessageBox();
}

/**
 * Clears all messages from the MessageBox and hides it if visible.
 **/
TrMessageBox.prototype.removeMessages = function(inputId)
{
  var listElement = this._getMessageList();
  
  if (!listElement || !listElement.hasChildNodes())
    return;
    
  var lineName = this._getMessageNameForInput(inputId);
  
  // Get the child 'li' elements
  var children = listElement.getElementsByTagName("li");
  for (var i = 0; i < children.length; ) 
  {
    var child = children[i];
    
    // if name matches the one we gave it in addMessage()
    if (child.name && child.name == lineName)
    {
      listElement.removeChild(child);
      // List is live, so it will re-index after removeChild
      // so don't increment index
      continue;
    }
    i++;
  }

  // Hide the MessageBox if empty  
  if (children.length == 0)
    this._hideMessageBox();
  else if (children.length == 1)
    // Set the style for a list of only 1 entry
    listElement.className = TrMessageBox._LIST_SINGLE_STYLE;
  else
    // Set the style for a multi-entry list
    listElement.className = TrMessageBox._LIST_STYLE;
}

TrMessageBox.prototype._getMessageBox = function()
{
  if (this._messageBoxId == null)
    return null;
  return _getElementById(document, this._messageBoxId);
}

TrMessageBox.prototype._getMessageList = function()
{
  if (this._messageBoxId == null)
    return null;
  return _getElementById(document, this._messageBoxId + "__LIST__");
}

TrMessageBox.prototype._showMessageBox = function()
{
  var messageBox = this._getMessageBox();
  if (!messageBox)
    return;

  messageBox.style.display = "";        
}

TrMessageBox.prototype._hideMessageBox = function()
{
  var messageBox = this._getMessageBox();
  if (!messageBox)
    return;

  messageBox.style.display = "none";        
}

// Generates a name for this message based on the id of the messagebox
// and the input it relates to.
TrMessageBox.prototype._getMessageNameForInput = function(inputId)
{
  if (!this._messageBoxId || !inputId)
    return null;
  return this._messageBoxId + "__" + inputId + "__";
}

/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */

// Flag used by partial page rendering and the back issue
// to indicate whether or not we need to restore the saved inline scripts.
var _pprBackRestoreInlineScripts = false;

// _pprBlocking is true if we're blocked waiting on a PPR event
var _pprBlocking = false;

// ER 4014884: block on every submit if requested.
var _blockOnEverySubmit = false;

// Controls whether or not Trinidad will allow the first click to go through in
// certain instances. When a PPR event occurs, we block all subsequent user
// input until it completes. However, there may be instances where the client
// wants to receive the very first click. For example, If the user has entered
// text in a textInput field with autoSubmit attached, then
// immediately clicked a submit button two events will be triggered - an
// onChange followed by an onClick. The onChange will trigger the autoSubmit
// which will immediately start the PPR blocking, so the onClick will get
// consumed by the blocking code and no submit will occur. Setting this value
// to true will allow the click to go through. This value can be controlled by
// the firstClickPassed attribute on the body element.
// TODO: Because PPR is now queued, is this still relevant?
var _pprFirstClickPass = false;

// We block using a special DIV element. This is its name
var _pprdivElementName = 'tr_pprBlockingDiv';

// stores the variables needed to load the libraries for IE
var _pprLibStore;


// The time at which we started the latest PPR block
var _pprBlockStartTime = 0;


// A holder for the pending timeout (Gecko only).
var _pprBlockingTimeout = null;

// Keeps track of the last element to initiate a PPR request
var _pprEventElement = null;

// We block input while a PPR request is in transit.
// _pprSavedCursor holds the cursor that the client had before the PPR event
// and _pprSavedCursorFlag keeps track of if we set _pprSavedCursor.
// var _pprSavedCursor = null;
var _pprSavedCursorFlag = false;

// Keeps track of whether the user has actually made a choice from the popup
var _pprChoiceChanged = false;


// Object containing information about the user agent
var _agent = new Object();

// Object for the last time we submitted
var _lastDateSubmitted;

// Object for the last time we reset a form
var _lastDateReset = 0;

// Variables tracking the last time we validated a field, and the last time the
// validation actually failed.
var _lastDateValidated  = 0;
var _lastValidationFailure  = 0;


// Keeps track of arguments that will be needed for a delayed event
var _delayedEventParams = new Object();

// this is the id of the component which gets the initial focus when the
// page loads.
var _initialFocusID = null;

// Certain Trinidad facilities can request that focus be set to a particular node,
// or the node AFTER a particular node following a PPR update. These three
// variables store the values needed to track down that node.
var _TrFocusRequestDoc = null;
var _TrFocusRequestID = null;
var _TrFocusRequestNext = false;

// Flag to indicate if inline validation is event based.
var _TrEventBasedValidation = false;

// _checkUnload is set on our body tag and is called when the window
// unloads. In our dialog windows, we call _checkUnload via an intermediary
// function _unloadADFDialog to work around Google's pop-up blocker
// feature which blocks onUnload event handlers from window's opened
// with window.open.
// We don't want to call _checkUnload twice in a row,
// once from dialog code and once from body's onUnload event handler,
// so we block the second call in _unloadADFDialog.
var _blockCheckUnloadFromDialog = false;


// variables needed if _submitForm was called before the form had
// completely rendered.
var _saveForm = null;
var _saveDoValidate = null;
var _saveParameters = null;
var _submitRejected = false;
var _inPartialSubmit = false;

// Flag used for timing issues with radioButtons
var _pendingRadioButton = false;

// List of mouse event names to capture
var _IE_MOUSE_CAPTURE_EVENTS = [
  "onclick",
  "ondblclick",
  "onmousedown",
  "onmousemove",
  "onmouseout",
  "onmouseover",
  "onmouseup"
  ];

// List of mouse event names to capture
var _GECKO_MOUSE_CAPTURE_EVENTS = [
  "click",
  "mousedown",
  "mouseup",
  "mouseover",
  "mousemove",
  "mouseout",
  "contextmenu"
  ];

/**
 * Return true if the agent is at least the specified agent type and version.
 */
function _atLeast(
  kind,
  version
  )
{
  return (!kind    || (kind    == _agent.kind))    &&
         (!version || (version <= _agent.version));
}


/**
 * Return true if the agent is at most the specified agent type and version.
 */
function _atMost(
  kind,
  version
  )
{
  return (kind == _agent.kind) && (version >= _agent.version);
}

function _supportsDOM()
{
  var retVal = false;

  if (_agent.isIE)
  {
    retVal = _agent.version >= 5.5;
  }
  else if (_agent.isNav)
  {
    retVal = false;
  }
  else if (_agent.isGecko || _agent.isSafari || _agent.isOpera)
  {
    retVal = true;
  }
  else if(_agent.isBlackBerry)
  {
    retVal = false;
    retVal = _agent.version >= 4.6;
  }

  return retVal;
}

/**
 * initialize information about the agent
 */
function _agentInit()
{
  // convert all characters to lowercase to simplify testing
  var agentString = navigator.userAgent.toLowerCase();

  // note that this only returns m.n (e.g. if the
  // version number is 2.3.4, this returns the float 2.3)
  var version = parseFloat(navigator.appVersion);

  // note that isBlackBerry refers to the BlackBerry browser
  // we do not currently specify the BlackBerry platform
  // because it is not necessary (if we decide to support
  // other browsers on the BlackBerry platform it may become necessary)
  var isBlackBerry      = false;
  var isGecko           = false;
  var isIE              = false;
  var isMac             = false;
  var isNav             = false;
  var isOpera           = false;
  var isPIE             = false;
  var isSafari          = false;
  var isSolaris         = false;
  var isWindows         = false;
  var isWindowsMobile6  = false;
  var isNokiaPhone      = false;
  var kind              = "unknown";

  // Group IE and IE based browsers such as IE Mobile on WM5 and WM6
  var isIEGroup         = false;

  // Indicate browser's PPR capability support
  var pprUnsupported    = false;

  // Flag to indicate that document object is sufficiently implemented to
  // provide good level of access to HTML, XHTML and XML document.
  // For example, Windows Mobile 5 and Blackberry does not implement
  // document.body, document.forms or document.documentElement and this
  // flag is set to false. Some features implemented in Core.js and
  // Page.js are not executed for the browsers when it is set to false.
  var supportsDomDocument = true;

  // Identifies browsers that do not support node.nodeType
  var supportsNodeType    = true;

  // Indicate browser's validation capability support
  var supportsValidation  = true;

  if (agentString.indexOf("msie") != -1)
  {
    // extract ie's version from the ie string
    var matches = agentString.match(/msie (.*);/);
    version = parseFloat(matches[1]);
    isIEGroup = true;

    // All IE based mobile browsers
    if (agentString.indexOf("windows ce") != -1)
    {
      supportsNodeType = false;
      supportsDomDocument = false;
      supportsValidation = false;

      // PPC200X and Windows Mobile 5
      if (agentString.indexOf("ppc") != -1 &&
          version >= 4.0)
      {
        // This user agent indicates the browser is WindowsMobile 5 or
        // earlier version of PIE
        isPIE = true;

        // Windows Mobile 5 DOM and XMLHttpRequest support are not
        // sufficient to support PPR in this framework effectively.
        pprUnsupported = true;
        kind = "pie";
      }
      else
      {
        // This user agent indicates the browser is WindowsMobile 6 PIE
        isWindowsMobile6 = true;
        // A new kind string was given to WM6 browser as the
        // capability is significantly different from predecessors.
        kind = "iemobile";
      }
    }
    else
    {
      isIE = true;
      kind = "ie";
    }
  }
  else if (agentString.indexOf("opera") != -1)
  {
    isOpera = true;
    kind = "opera";
  }
  else if ((agentString.indexOf("applewebkit") != -1) ||
           (agentString.indexOf("safari") != -1))
  {
    isSafari = true
    kind = "safari";
  }
  else if (agentString.indexOf("gecko/") != -1)
  {
    isGecko = true;
    kind = "gecko";
    version = 1.0;
  }
  else if(agentString.indexOf("blackberry") != -1)
  {
    // if we support non-BlackBerry Browser agents on blackberry
    // devices in the future, we may need to revisit this because
    // those agents may include "blackberry" in the User-Agent
    // string; we can't just check if the User-Agent "starts with"
    // blackberry because navigator.userAgent on BlackBery Browser 4.0
    // starts with Mozilla/4.0 (even though the User-Agent sent to the
    // server starts with BlackBerry<model>/<version>)

    // BlackBerry Browser 4.0+ supports navigator.appVersion,
    // and earlier versions don't support script, so we can
    // leave the version as defined above

    // BlackBerryXXXX/Y.Y.Y.Y is the BlackBerry user agent format
    // XXXX is the model number and Y.Y.Y.Y is the OS version number.
    // At this moment, BlackBerry below version 4.6 is regarded as
    // basic HTML browser for the JS performance reason.
    // The following lines should be uncommented when we decide to
    // handle BlackBerry version 4.0~4.5 separate from the batch of
    // Basic HTML browsers after its JS performance improves.
    /*
    var versionStart = agentString.substring(agentString.indexOf(
                                                      "blackberry") + 9);
    versionStart = versionStart.substring(versionStart.indexOf("/") + 1);
    version = parseFloat(versionStart);

    if (version < 4.6)
    {
      pprUnsupported = true;
      supportsDomDocument = false;
      supportsValidation = false;
    }
    */

    isBlackBerry = true;
    kind = "blackberry";
  }
  else if ((agentString.indexOf('mozilla')    != -1) &&
           (agentString.indexOf('spoofer')    == -1) &&
           (agentString.indexOf('compatible') == -1))
  {
    if (version >= 5.0)
    {
      isGecko = true;
      kind = "gecko";
      version = 1.0;
    }
    else
    {
      isNav = true;
      kind = "nn";
    }
  }
  if (agentString.indexOf('win') != -1)
  {
    isWindows = true;
  }
  else if (agentString.indexOf('mac') != -1)
  {
    isMac = true;
  }
  else if (agentString.indexOf('sunos') != -1)
  {
    isSolaris = true;
  }
  else if ((agentString.indexOf('symbian') != -1) ||
           (agentString.indexOf('nokia') != -1))
  {
     isNokiaPhone = true;
     pprUnsupported = true;
  }

  _agent.isBlackBerry           = isBlackBerry;
  _agent.isGecko                = isGecko;
  _agent.isIE                   = isIE;
  _agent.isIEGroup              = isIEGroup;
  _agent.isMac                  = isMac;
  _agent.isNav                  = isNav;
  _agent.isNokiaPhone           = isNokiaPhone;
  _agent.isOpera                = isOpera;
  _agent.isPIE                  = isPIE;
  _agent.isSafari               = isSafari;
  _agent.isSolaris              = isSolaris;
  _agent.isWindows              = isWindows;
  _agent.isWindowsMobile6       = isWindowsMobile6;
  _agent.kind                   = kind;
  _agent.pprUnsupported         = pprUnsupported;
  _agent.supportsDomDocument    = supportsDomDocument;
  _agent.supportsNodeType       = supportsNodeType;
  _agent.supportsValidation     = supportsValidation;
  _agent.version                = version;

  _agent.atLeast                = _atLeast;
  _agent.atMost                 = _atMost;
}


_agentInit();

// available features in ie
var _ieFeatures =
{
  channelmode:1, // ie 5.0
  copyhistory:1,
  directories:1,
  fullscreen:1,  // ie 5.0
  height:1,
  location:1,
  menubar:1,
  resizable:1,
  scrollbars:1,
  status:1,
  titlebar:1,    // ie 5.0 when trusted
  toolbar:1,
  width:1
};

// available features in Netscape
var _nnFeatures =
{
  alwayslowered:1,
  alwaysraised:1,
  copyhistory:1,
  dependent:1,
  directories:1,
  height:1,
  hotkeys:1,
  innerheight:1,
  innerwidth:1,
  location:1,
  menubar:1,
  outerwidth:1,
  outerheight:1,
  resizable:1,
  scrollbars:1,
  status:1,
  titlebar:1,
  toolbar:1,
  width:1,
  "z-lock":1
}

// override values for modeless windows. Values in this
// list can't be overridden by the caller for modeless windows
var _modelessFeatureOverrides =
{
};

// override values for modal windows. Values in this
// list can't be overridden by the caller for modal windows
var _modalFeatureOverrides =
{
};


var _featureDefaults =
{
  // default values for features of document windows
  document:
  {
    channelmode:false,
    copyhistory:true,
    dependent:false,
    directories:true,
    fullscreen:false,
    hotkeys:false,
    location:true,
    menubar:true,
    resizable:true,
    scrollbars:true,
    status:true,
    toolbar:true
  },
  // default values for features of dialog windows
  dialog:
  {
    channelmode:false,
    copyhistory:false,
    dependent:true,
    directories:false,
    fullscreen:false,
    hotkeys:true,
    location:false,
    menubar:false,
    resizable:true,
    scrollbars:true,
    status:true
  }
}


// featues that require signbing in order to be set
var _signedFeatures =
{
  alwayslowered:1,
  alwaysraised:1,
  titlebar:1,
  "z-lock":1
};

// features that are boolean values
var _booleanFeatures =
{
  alwayslowered:1,
  alwaysraised:1,
  channelmode:1,
  copyhistory:1,
  dependent:1,
  directories:1,
  fullscreen:1,
  hotkeys:1,
  location:1,
  menubar:1,
  resizable:1,
  scrollbars:1,
  status:1,
  titlebar:1,
  toolbar:1,
  "z-lock":1
};


/**
 * Adds an event handler to an element.
 * @param obj The element against which the event handler should be registered
 * @param exType The event handler type such as 'change', 'blur', 'click' etc.
 * @param fn The function to call when the event occurs
 */
function _addEvent(obj, evType, fn)
{
  if (obj.addEventListener)
  {
    obj.addEventListener(evType, fn, false);
    return true;
  }
  else if (obj.attachEvent)
  {
    var r = obj.attachEvent("on"+evType, fn);
    return r;
  }
  else
  {
    return false;
  }
}

/**
 * Removes an event handler from an element.
 * @param obj The element against which the event handler is regsitered
 * @param exType The event handler type such as 'change', 'blur', 'click' etc.
 * @param fn The event handler function to remove from the element
 */
function _removeEvent(obj, evType, fn)
{
  // TODO: abstract onto Agent object
  if (obj.removeEventListener)
  {
    obj.removeEventListener(evType, fn, false);
    return true;
  }
  else if (obj.detachEvent)
  {
    var r = obj.detachEvent("on"+evType, fn);
    return r;
  }
  else
  {
    return false;
  }
}

/**
 * Gets the preferred width of the content
 */
function _getBodyWidth(
  element,
  offsetWidth,
  offsetLeft
  )
{
  var maxWidth = _getContentWidth(element, offsetWidth, 0);

  // bogusly double the offset to guess the right margin...
  // However, in right to left languages, the left margin can get very large,
  // and cause the window to become huge (Bug 2846393). Limit it to an
  // empirically reasonable value.
  var marginWidth = 10;

  if (_isLTR() || (offsetLeft <= 5))
  {
      marginWidth = 2 * offsetLeft;
  }

  return maxWidth + marginWidth;
}

/**
 * Gets the preferred width of the content
 */
function _getContentWidth(
  element,
  offsetWidth,
  offsetLeft
  )
{
  var children = element.childNodes;

  // XXXSafari: what to do here?
  var isIE = _agent.isIE;

  var hasContentProp = (isIE)
                         ? "canHaveHTML"
                         : "tagName";
  var maxWidth = 0;

  for (var i = 0; i < children.length; i++)
  {
    var currChild = children[i];

    if (currChild[hasContentProp] && (currChild.offsetWidth > 0))
    {
      var currWidth = 0;
      var currOffsetWidth = currChild["offsetWidth"];

      if (!isIE)
      {
        if ((currOffsetWidth == offsetWidth) ||
            (currOffsetWidth <= 1))
        {
          var currOffsetLeft = currChild.offsetLeft;
          if (currChild.parentNode != currChild.offsetParent)
          {
            currOffsetLeft = currOffsetLeft -
                             (currChild.parentNode.offsetLeft);
          }

          currWidth = _getContentWidth(currChild,
                                       currOffsetWidth,
                                       currOffsetLeft);
        }
        else
        {
          currWidth = currOffsetWidth;
        }
      }
      else
      {
        currWidth = currChild["clientWidth"];

        if (currWidth == 0)
        {
          var currOffsetLeft = currChild.offsetLeft;
          if (currChild.parentElement != currChild.offsetParent)
          {
            currOffsetLeft = currOffsetLeft -
                             (currChild.parentElement.offsetLeft);
          }

          currWidth = _getContentWidth(currChild,
                                       currOffsetWidth,
                                       currOffsetLeft);
        }
      }

      if (currWidth > maxWidth)
      {
        maxWidth = currWidth;
      }
    }
  }

  // handle error cases
  if (maxWidth == 0)
    maxWidth = offsetWidth;

  return maxWidth + offsetLeft;
}

/**
 * Safely returns the parent window of a window, or undefined if security doesn't allow us to
 * retrieve the parent
 */
function _getParentWindow(currWindow)
{
  var parentWindow = currWindow.parent;

  try
  {
    // dummy read to test security error
    parentWindow.name;

    return parentWindow;
  }
  catch (e)
  {
    return undefined;
  }
}

/**
 * Safely retrieve the top accessible window
 */
function _getTop(currWindow)
{
  // since top might be in another domain, crawl up as high as possible manually
  var currParentWindow = _getParentWindow(currWindow);

  while (currParentWindow && (currParentWindow != currWindow))
  {
    currWindow = currParentWindow;
    currParentWindow = _getParentWindow(currWindow);
  }

  return currWindow;
}


/**
 * renders transparent image for spacing
 */
function t(width,height)
{

  // if the transparent url is not null render img tag
  if (_tURL)
  {
    document.write('<img src="' + _tURL + '"');

    if (width)
      document.write(' width="' + width + '"');
    if (height)
      document.write(' height="' + height + '"');

    // if accessibility mode is not null, render alt attribute
    if (_axm)
      document.write(' alt=""');

    document.write('>');
  }
}



/**
 * Returns the object containing the dependent windows.
 */
function _getDependents(
  parentWindow,
  createIfNecessary
  )
{
  var depends;

  if (parentWindow)
  {
    depends = parentWindow["_dependents"];

    if (!depends)
    {
      if (createIfNecessary)
      {
        depends = new Object();
        parentWindow["_dependents"] = depends;
      }
    }
  }

  return depends;
}

/**
 * Get the named dependent
 */
function _getDependent(
  parentWindow,
  dependentName
  )
{
  var depends = _getDependents(parentWindow);
  var dependent;

  if (depends)
  {
    dependent = depends[dependentName];
  }

  return dependent;
}


/**
 * Sets the value of the named dependent
 */
function _setDependent(
  parentWindow,
  dependentName,
  dependentValue
  )
{
  var depends = _getDependents(parentWindow, true);

  if (depends)
  {
    depends[dependentName] = dependentValue;
  }
}


/**
 * Returns the dependent which is modal.
 */
function _getModalDependent(
  parentWindow
  )
{
  return _getDependent(parentWindow, "modalWindow");
}


/**
 * Returns true if the passed in dependent is the modal dependent
 * of the parent window,
 */
function _isModalDependent(
  parentWindow,
  dependent
  )
{
  return (dependent == _getModalDependent(parentWindow));
}


/**
 * Called by our modal windows when changes are applied and
 * the window is closed to make sure that the parent window
 * is updated appropriately. Due to bug 3184718 (Google
 * pop-up blocker does not call onUnload event handlers from
 * dialog windows) we cannot rely on _checkUnload being
 * called by the unload event. We call this function
 * instead which in turn calls _checkUnload.
 */
function _unloadADFDialog(
  event
  )
{
  // _checkUnload is called from body's
  // unload event when
  // We use this flag to keep it from
  // running through _checkUnload function twice.
  // If Google's pop-up blocker is
  // not enabled, then _checkUnload is called from body's
  // unload event as well as from here.

  _blockCheckUnloadFromDialog = false;
  _checkUnload(event);
  _blockCheckUnloadFromDialog = true;
}

/**
 * Called by the unload handler of modal windows to call the event
 * handler and make sure that the parent window is updated appropriately
 * to show that no modal window is up anymore.
 */
function _checkUnload(
  event
  )
{
  //PH:set the right event object;
  event = _getEventObj();

  // Make sure we don't run through this function twice
  // when we close a dialog. The
  // _unloadADFDialog function blocks a second run
  // using the _blockCheckUnloadFromDialog flag.

  if (_blockCheckUnloadFromDialog)
  {
    _blockCheckUnloadFromDialog = false;
    return;
  }

  // Check to see if we are a modal window that has been
  // abandoned (who's parent has changed out from under us).
  // In this case, we skip the unload handler, since the
  // modal window no longer has permission to access its
  // parent, and JavaScript errors may occur
  if (_isModalAbandoned())
    return;

  // Check to see if we have an open modal child window
  var modalWindow = _getModalDependent(window);
  if (modalWindow != null)
  {
    // If we are being unloaded before our modal child has been
    // closed, that means that the user must have navigated
    // to a new page just before the modal window was displayed.
    // In this case, let's just close our modal child.  We need
    // to be extra careful to make sure that the modal child does
    // not try to access any properties on the parent window
    // when closing, because the modal child may no longer have
    // permission to access us at this point.  Set a property
    // on the modal window to let it know that it has been
    // abandoned.
    _setModalAbandoned(modalWindow);

    // Now we can safely close the modal window
    modalWindow.close();
  }

  var topWindow = _getTop(self);

  if (!topWindow)
    return;

  var parentWindow = topWindow["opener"];

  if (!parentWindow)
    return;

  var unloader = _getDependent(parentWindow, self.name);

  if (_isModalDependent(parentWindow, self))
  {
    // remove the modal window
    _setDependent(parentWindow, "modalWindow", (void 0));

    parentWindow.onfocus = null;

    if (_agent.supportsDomDocument)
    {
      var parentBody = parentWindow.document.body;

      // release the ie mouse grab
      if (_agent.atLeast("ie", 4))
      {
        if (_agent.atLeast("ie", 5) && _agent.isWindows)
        {
          parentBody.onlosecapture = null;

          _removeModalCaptureIE(parentBody);
        }
        parentBody.style.filter = null;
      }

      if (_agent.isGecko)
      {
        if (parentBody != (void 0))
        {
          _removeModalCaptureGecko(parentWindow, parentBody);
        }
      }
    }
  }

  if (unloader != (void 0))
  {
    // remove our dependent info
    _setDependent(parentWindow, self.name, (void 0));

    // try the passed in event (netscape way first), then
    // try to get the event the IE way
    if (event == (void 0))
      event = self.event;

    // call the unloader with the unloading window and the event
    unloader(topWindow, event);
  }
}

// Adds a (IE-specific) capture to the specified element
// for blocking mouse events during modal dialog display
function _addModalCaptureIE(element)
{
  // Captured events still bubble on IE.  Register
  // mouse event handlers to cancel event bubbling
  // and save away old listeners so we can restore
  // them when the capture is removed.
  var savedListeners = new Object();
  var events = _IE_MOUSE_CAPTURE_EVENTS;
  var eventCount = events.length;

  for (var i = 0; i < eventCount; i++)
  {
    var eventName = events[i];
    savedListeners[eventName] = element[eventName];
    element[eventName] = _captureEventIE;
  }

  // Stash away the saved listener somewhere where
  // we can get at them later
  window._modalSavedListeners = savedListeners;

  // Set the capture
  window._trIeCapture = element;
  window._trIeCaptureCurrent = true;
  element.setCapture();
}

// Removes a (IE-specific) capture added via _addModalCaptureIE()
function _removeModalCaptureIE(element)
{
  // Release the capture
  element.releaseCapture();

  // Restore event handlers that were saved away
  // during _addModalCaptureIE().
  var savedListeners = window._modalSavedListeners;

  if (savedListeners)
  {
    var events = _IE_MOUSE_CAPTURE_EVENTS;
    var eventCount = events.length;

    for (var i = 0; i < eventCount; i++)
    {
      var eventName = events[i];

      element[eventName] = savedListeners[eventName];
    }

    window._modalSavedListeners = null;
  }
  window._trIeCapture = undefined;
}

// Captures (and consumes) events during modal grabs
// on IE browsers
function _captureEventIE()
{
  // do not capture events outside the document body, this leads to the inability for users
  // to click on IE toolbars, menubars, etc.
  var event = window.event;
  if (event.screenY >= window.screenTop && event.screenX >= window.screenLeft)
  {
    if (!window._trIeCaptureCurrent && window._trIeCapture)
    {
      window._trIeCaptureCurrent = true;
      window._trIeCapture.setCapture();
    }
    event.cancelBubble = true;
  }
  else if (window._trIeCapture)
  {
    window._trIeCaptureCurrent = false;
    window._trIeCapture.releaseCapture();
  }
}

// Adds a (Gecko-specific) capture to the specified element
// for blocking mouse events during modal dialog display
function _addModalCaptureGecko(element)
{
  var events = _GECKO_MOUSE_CAPTURE_EVENTS;
  var eventCount = events.length;

  for (var i = 0; i < eventCount; i++)
  {
    var eventName = events[i];
    element.addEventListener(eventName, _captureEventGecko, true);
  }
}

// Removes a (Gecko-specific) capture added via _addModalCapture()
function _removeModalCaptureGecko(parentWindow, element)
{
  var events = _GECKO_MOUSE_CAPTURE_EVENTS;
  var eventCount = events.length;

  for (var i = 0; i < eventCount; i++)
  {
    var eventName = events[i];
    element.removeEventListener(eventName,
                                parentWindow._captureEventGecko,
                                true);
  }
}

// Captures (and consumes) events during modal grabs
// on Gecko browsers
function _captureEventGecko(
  event
  )
{
  // Stop propagation and suppress default action
  event.stopPropagation();
  window.preventDefault = true;
}

// Tests whether the current window is an "abandoned"
// modal window.  This is a modal window who's parent
// window has navigated to a new page, in which case
// the modal window is out of context.
function _isModalAbandoned()
{
  // We look for the _abandoned property on the modal window.
  // Note that in the LOV case, we actually have two onunload
  // event handlers that need to be suppressed: The onunload
  // handler for the LOV window and the onunload handler for
  // the actual contents of the LOV window which are nested
  // within a frame.  So, we check for the _abandoned property
  // on the "top" window.
  var topWindow = _getTop(self);

  return topWindow._abandoned;
}

// Marks the specified modal window as abandoned
function _setModalAbandoned(
  modalWindow
  )
{
  // Just set the _abandoned property on the modal window.
  modalWindow._abandoned = true;
}


/**
 * Function that returns a single key/value pair String
 */
function _getKeyValueString(
  target,
  keyName,
  index
  )
{
  var value = target[keyName];

  if (typeof(value) == "function")
  {
    value = "[function]";
  }

  // XXXSafari: what to do here?
  var separator = (_agent.isGecko)
                    ? ((index + 1) % 3 == 0)
                      ? '\n'
                      : '    '
                    : '\t';

  return keyName + ':' + value + separator;
}

function _dumpSuppress(
  target
  )
{
  _dump(target, {innerText:1, outerText:1, outerHTML:1, innerHTML:1});
}

/**
 * Utility for dumping the contents of a JavaScript object.
 */
function _dump(
  target,
  suppressProps,
  name
  )
{
  var props = "";

  if (target)
  {
    // default the name if none provided
    if (!name)
    {
      name = target["name"];
    }

    //
    // Because we need to catch exceptions that IE throws if
    // for some object reads, we need to have separate ie and netscape
    // code for the exception catching.  Unfortunately, "try" and
    // catch are reserved words, so we have to dynamically create
    // our adding function so that Netscape doesn't throw it's own
    // exception when it parses our file
    //
    // var adderContent = "return i + ':' + target[i] + '\\n';";
    var adderContent = "return _getKeyValueString(target, key, index);";

    // wrap adder content with a try and eat the bogus ie exception
    if (_agent.atLeast("ie", 5) || _agent.isGecko || _agent.isSafari)
      adderContent = "try{" + adderContent + "}catch(e){return '';}";

    var adder = new Function("target", "key", "index", adderContent);
    var propCount = 0;
    var propArray = new Array();

    for (var key in target)
    {
      // don't add properties that should be suppressed
      if ((!suppressProps || !suppressProps[key]) && !key.match(/DOM/))
      {
        propArray[propCount] = key;
        propCount++;
      }
    }

    // sort the array so that we can find stuff
    propArray.sort();

    for (var i = 0; i < propArray.length; i++)
    {
      props += adder(target, propArray[i], i);
    }
  }
  else
  {
    // the object to dump was undefined
    name = "(Undefined)";
  }

  // tell the user that the object has no properties
  if (props == "")
  {
    props = "No properties";
  }

  alert(name + ":\n" + props);
}

function _getJavascriptId(name)
{
  return name.split(':').join('_');
}

function _getFormName(form)
{
  var name = form.name;

  if ((typeof name) != 'string')
  {
    if (_agent.isIE)
    {
      name = form.attributes['name'].nodeValue;
    }
    else
    {
      name = form.getAttribute('name');
    }
  }

  return name;
}


/**
 * Calls the correct validations function for the form and returns true
 * if the validation succeeded.
 */
function _validateForm(
  form,
  source
  )
{
  // Blackberry does not set valiation script in the fields
  // and it is not possible to execute validation.
  // Return true if validation is not supported.
  if (!_agent.supportsValidation)
  {
    return true;
  }

  var funcName = '_' + _getJavascriptId(_getFormName(form)) + 'Validator';
  var formWind = window[funcName];
  if (formWind)
  {
    try
    {
      ret = formWind(form, source);
    }
    catch (e)
    {
      // Validator did not execute normally.
      // In case for mobile devices, BlackBerry, Nokia, Windows Mobile and PPC
      // return true in order to submit the form.
      if (_agent.isPIE || _agent.isNokiaPhone || _agent.isBlackBerry)
      {
        ret = true;
      }
      else
      {
        ret = false;
      }
    }
      return ret;
  }

  return false;
}


/**
 * Validate the specified field.
 */
function _valField(
  formName,
  nameInForm
  )
{
  if (nameInForm)
  {
    // get the target whose validation we want to run
    var target = document.forms[formName][nameInForm];

    // get its onblur function
    var blurFunc = target.onblur;

    if (blurFunc)
    {
      // FIXME: this is *NOT* portable.  Safari, for example,
      // does not implement toString() on functions this way,
      // and nothing requires that it does
      var valFunc = blurFunc.toString();

      // whack off the beginning and end of the function, leaving the content
      var valContents = valFunc.substring(valFunc.indexOf("{") + 1,
                                          valFunc.lastIndexOf("}"));

      var targetString = "document.forms['" +
                         formName +
                         "']['" +
                         nameInForm +
                         "']";

      // replace 'this' with the actual target
      valContents = valContents.replace(/this/, targetString);

      // trim off last argument
      var lastArg = valContents.lastIndexOf(",");

      valContents = valContents.substring(0, lastArg) + ")";

      // perform the validation
      eval(valContents);
    }
  }
}

function _validateAlert(
  form,
  source,
  validators,
  globalMessage,
  errorTitle
  )
{
  if (!validators)
    validators = _getValidators(form);

  var failureMap = _multiValidate(form, source,  validators);

  var firstFailure = true;
  var failureString = errorTitle + '\n';

  for (var currId in validators)
  {
    // Get the messages array for currId, skip if none
    var messages = failureMap[currId];
    if (!messages || messages.length==0)
      continue;

    // Get the input element
    var currInput = _getFormElement(form, currId);
    if (!currInput)
      continue;

    // Get the label text for this input
    var label = validators[currId].label;

    // Loop through the messages for this input
    for (var j=0; j < messages.length; j = j+2)
    {
      // Move the focus back to the first failed field
      if (firstFailure)
      {
        _setFocus(currInput);
        firstFailure = false;
      }

      // Get the current message
      var facesMessage = messages[j];

      if (_agent.isNokiaPhone)
      {
        errorString = _getGlobalErrorString(currInput,
                            globalMessage,
                            facesMessage,
                            label);
      }
      else
      {
        errorString = _getGlobalErrorString(currInput,
                            globalMessage,
                            facesMessage.getDetail(),
                            label);
      }

      failureString += errorString + '\n';
    }
  }

  if (firstFailure)
    return true;

  // Show the error and note the time we finished this validation.
  // Record the validation both before and after the alert so that we
  // halt any validations caused by events triggered along with this
  // one, or by the closing of this alert.
  _recordValidation(true, 0);
  alert(failureString);
  _recordValidation(true, 0);

  return false;
}

function _validateInline(
  form,
  source,
  validators
  )
{
  // If not passed explicitly, return
  if (!validators)
    validators = _getValidators(form);

  var failureMap = _multiValidate(form, source,  validators);

  var noFailures = true;

  for (var currId in validators)
  {
    var foundMsg = false;

    // Get the icon if any
    var iconElem = _getElementById(document, currId + "::icon");

    // If component hasn't got a message element, then skip
    var msgElem = _getElementById(document, currId+ "::msg");

    // Clear any existing inline message
    if (msgElem && msgElem.innerHTML !="")
      msgElem.innerHTML = "";

    // Clear any existing messages from the MessageBox
    TrMessageBox.removeMessages(currId);

    // Get the messages array for currId, skip if none
    var messages = failureMap[currId];
    if (!messages || messages.length==0)
    {
      // Hide the inline message and icon
      if (msgElem)
        msgElem.style.display = "none";
      if (iconElem)
        iconElem.style.display = "none";
      continue;
    }

    // Get the input element
    var currInput = _getFormElement(form, currId);
    if (!currInput)
      continue;

    // Get the label text for this input
    var label = validators[currId].label;

    // Loop through the messages for this input
    for (var j=0; j < messages.length; j = j+2)
    {
      if (noFailures)
      {
        noFailures = false;

        // Move the focus back to the first failed field
        // TODO - Remove once inline validation uses onblur/onchange
        _setFocus(currInput);
      }

      // Get the current message
      var facesMessage = messages[j];

      if (msgElem)
      {
        if (_agent.isNokiaPhone)
        {
          msgElem.innerHTML = facesMessage;
        }
        else
        {
          msgElem.innerHTML = facesMessage.getDetail();
        }
      }

      // if there's nowhere to display the message in either
      // summary or detail, then pop an alert to warn the page developer
      if (!msgElem && !TrMessageBox.isPresent())
      {
        if (_agent.isNokiaPhone)
        {
          alert("Field Error [" + currId + "] - " + facesMessage);
        }
        else
        {
          alert("Field Error [" + currId + "] - " + facesMessage.getDetail());
        }
      }

      // Add the message to the MessageBox
      TrMessageBox.addMessage(currId, label, facesMessage);
    }

    // If we got this far, we know there's something to display so
    // make the inline message and icon visible.
    if (msgElem)
      msgElem.style.display = "";
    if (iconElem)
      iconElem.style.display = "";
  }

  return noFailures;
}

/**
 * Performs validation on the supplied input element.  If validation fails
 * the appropriate message will be displayed in the message component for this
 * input field.
 * <p>
 * The simplest usage of this method is from the onblur attribute of the
 * input component. e.g. onblur="_validateInput(event);"
 * <p>
 * @param event(Event) The event object provided by the event handler.
 * @param falseOnFail(boolean) Force method to return false if validation failed.
 * @return boolean, false if validation failed and falseOnFail set to true, otherwise true.
 */
// TODO: make this a public function only after hanging it on
// a namespaced object, *and* making it not specific to inline
// validation
function _validateInput(event, falseOnFail)
{
  if (!event)
    return true;

  // Get the element associated with the event
  var inputElem = event.target || event.srcElement;

  if (!inputElem || !inputElem.id)
    return true;

  var form = _getForm(inputElem);
  if (!form)
    return true;

  var validators = _getValidators(form);
  if (!validators)
    return true;

  var id = inputElem.id;

  var descriptor = validators[id];

  // If we couldn't find the descriptor by id, then try by name
  // as it might be a radio button
  if (!descriptor && inputElem.name)
  {
    id = inputElem.name;
    descriptor = validators[id];
  }
  if (!descriptor)
    return true;

  // Create a new temporary validators object and run with just the
  // one descriptor
  var validatorsToRun = new Object();
  validatorsToRun[id] = descriptor;

  // Call inline validation using only the appropriate validators
  var retval = _validateInline(form, null, validatorsToRun, 1, null);

  // Only return the actual outcome if asked to do so
  if (falseOnFail)
    return retval;
}

// Records the time of this validation event.
// If fail is set, this is a validation failure, that is noted also.
function _recordValidation(fail, now)
{
  if (!now)
    now = new Date();

  _lastDateValidated = now;
  if (fail)
    _lastValidationFailure = now;
}


// returns true if a validation has occurred "recently"
// failures: True means only report on recent failures, false means report on
//           any validation.
function _recentValidation(failures)
{
  var retVal = false;
  var timeWindow = 250;

  // Assuming that a reasonable user won't close the dialog and change the
  // text within a quarter of a second, we ignore any validations within
  // 250ms. of the last failed validation. The timings I've seen here range
  // in the 60 - 90 ms. range, but that is on fast development machines. We
  // could probably lower this to 150 if we're seeing dropped validations
  // for really fast, ambitious users.
  // With Macintosh IE, we manage to crash the browser!
  if (_agent.isMac)
  {
    // The iBook can have diffs of up to about 480 ms.
    // Call it 600 to be safe.
    timeWindow = 600;
  }

  var newDate = new Date();
  var diff;

  // If failures are requested, the caller is only interested in failures.
  // If simple validation requested, caller interested in any validation fail
  // or not.
  diff = newDate - _lastValidationFailure;
  if ((diff >= 0) && (diff < timeWindow))
  {
    retVal = true;
  }
  else if (!failures)
  {
    diff = newDate - _lastDateValidated;
    if ((diff >= 0) && (diff < timeWindow))
    {
      retVal = true;
    }
  }
  return retVal;
}

/**
 * Used to submit a selected item in a choice as if it's a commandLink
 * or commandButton
 */
function _commandChoice(
  form,
  choice
)
{
  var src = document.forms[form].elements[choice].value;

  // need this strange [0] for when choice repeated,
  // for example a processChoiceBar in actions facet of panelPage.
  if (src == void(0))
    src = (document.forms[form].elements[choice])[0].value;

  // if it starts with a '#', it's an url.
  var gtIndex = src.indexOf("#");
  if ( gtIndex == 0)
    window.document.location.href = src.substring(1,src.length);
  else
  {
    var openBracketIndex = src.indexOf("[");
    var srcID = src.substring(0, openBracketIndex);
    var validateString = src.substring(openBracketIndex+1, openBracketIndex+2)
    var validate = parseInt(validateString);
    submitForm(form,validate,{source:srcID});
  }
}



/**
 * Attempts to submits the form, potentially firing validation and notifying
 * any Cabo onSubmit handlers registered on the form, returning
 * <code>true</code> if the submission actually occurred.
 * <p>
 * If the <code>doValidate</code> parameter is false, no validation will
 * be performed, and the form is guaranteed to be submitted.  Otherwise,
 * the form will be submitted if both the validation succeeds and any
 * registered Cabo onSubmit handlers do not return <code>false</code>.
 * <p>
 * @param form The form to submit.  This can either be the name of the form
 *             in the current <code>document</code>, the index of the form
 *             in the current <code>document</code> or the form itself.
 * @param doValidate boolean value specifying whether validation should
 *   occur before the form is submitted.  (As per a common Javascript
 *   idiom, it is acceptable to pass true/false as well as 0/1).  If
 *   this parameter is ommitted, it defaults to true.
 * @param parameters a single Javascript object that specifies
 *   all the additional key-value pairs to submit.  There must be
 *   pre-existing &lt;input type="hidden"&gt; elements as targets
 *   for each of these parameters.
 */
function submitForm(
  form,
  doValidate,
  parameters,
  isPartial
  )
{
  // If we've delayed any sort of event submission, we won't want to do it at
  // all now that the form is getting submitted. Blow away the saved data. Any
  // timeout handler will cancel the event submission if the data no longer
  // exists.
  var pending = true;
  if (_agent.isIEGroup)
  {
    pending = false;
    // keep track of whether there was a pending event
    for (var key in _delayedEventParams)
    {
      pending = true;
      break;
    }
  }

  if (pending)
  {
    _delayedEventParams = new Object();
    _delayedEventParams["reset"] = true;
  }

  // if the form was passed as a form name, get the form object
  if ((typeof form) == "string")
  {
    form = document[form];
  }
  // if the form was passed as a form index, get the form object
  else if ((typeof form) == "number")
  {
    form = document.forms[form];
  }

  // we had better have a form now
  if (!form)
    return false;

  // Check to see if submitForm is called before the form
  // has been rendered completely. If so, save the parameters
  // and return. At the end of the form, we always call _submitFormCheck
  // which will re-call submitForm if it had been rejected.
  // This is for bug 2752257.

  // Bug #3058770: In LovInput, we have to hack up a form for NLS. It's not
  // validated, so there is no real validator, we've just hacked one. The
  // submit always sets doValidate to false. Just make sure that you never use
  // this validator if doValidate is false (it might just be the value '1').
  var formComplete = window["_"+ _getJavascriptId(_getFormName(form)) + "Validator"];

  if (formComplete == (void 0))
  {
    _saveFormForLaterSubmit(form, doValidate, parameters);

    // Do not submit the form,
    // since the form hasn't been rendered completely yet.
    return false;
  }

  // Bug 1789483: ignore a second form submission that happens
  // less than 0.5 seconds after the first one
  var newDate = new Date();
  if (_recentSubmit(newDate))
  {
    // However if we're allowing the first click through... we queue up this
    // submit.
    if (_pprFirstClickPass && _pprBlocking)
    {
      _saveFormForLaterSubmit(form, doValidate, parameters);
    }
    return;
  }

  // just in case, clear it the delayed submit flags
  _submitRejected = false;
  _inPartialSubmit = false;

  _lastDateSubmitted = newDate;

  // default value for doValidate is true
  if (doValidate == (void 0))
    doValidate = true;

  // assume that we should submit the form
  var doSubmit = true;

  // validate the form if necessary, and don't submit the
  // form if validation fails
  var paramSource;
  if (parameters != null)
    paramSource = parameters.source;
  else
    paramSource = "";

  if (doValidate && !_validateForm(form, paramSource))
    doSubmit = false;

  //
  // If we have an onSubmit handler, call it
  //
  var onSubmit = window["_" + _getJavascriptId(_getFormName(form)) + "_Submit"];

  if (typeof onSubmit != "undefined" && doSubmit)
  {
    // create function so that "return" is handled correctly,
    var func = new Function("doValidate", onSubmit);
    var handlerResult;

    // WindowsMobile 5 doesn't support installing Funtion object
    // to "this", so just invoke the Function object instead.
    if (_agent.isPIE)
    {
      handlerResult = func(event);
    }
    else
    {
      // install the function on the object so that "this" is
      // handled correctly
      form._tempFunc = func;

      // call the submit handler with the doValidate flag,
      handlerResult = form._tempFunc(doValidate);

      // uninstall the temporary function
      form._tempFunc = (void 0);
    }
    // if we're validating and the handler returns false,
    // don't submit the form
    if (doValidate && (handlerResult == false))
    {
      doSubmit = false;
    }
  }

  if (doSubmit)
  {
    // reset any hidden form values before submitting
    TrPage.getInstance()._resetHiddenValues(form);

    // While WM6 can support PPR, WM5 and PPC lacks enough support
    // for DOM and/or HMLHTTP. Disable partial form post for PPC and
    // WM5.
    if (isPartial && _supportsPPR())
    {
      // In the case of Windows-mobile(WM) browsers, during rendering, 
      // Trinidad stores the value of the request-header field, UA-pixels,
      // into a hidden-parameter's value attribute. WM browsers' PPRs don't 
      // contain UA-pixels in their request-headers. So during a WM browser's 
      // PPR, we need to manually set the field, UA-pixels, into the 
      // request-header with the hidden parameter's value.
                  
      if (_agent.isPIE || _agent.isWindowsMobile6)
      {
        var header = new Array(1);
        header['UA-pixels'] = form.elements['uapixels'].value;
        TrPage.getInstance().sendPartialFormPost(form, parameters, header);
      }
      else
      {      
        TrPage.getInstance().sendPartialFormPost(form, parameters);
      }  
    }
    else
    {
      //
      // assign any dynamic values before submitting
      //
      var isDOM = _supportsDOM();
      var tempParams = new Object();

      if (parameters)
      {
        for (var paramName in parameters)
        {
          var paramValue = parameters[paramName];
          if (paramValue != (void 0))
          {
            // do not try to get properties from the form element directly.
            // Some code somewhere was setting an htmlInputElement as
            // a property on the formElement, but not as a child.
            // This was causing bug 4536656.
            // I can't yet figure out who is setting the htmlInputElement as
            // a property (instead of a child).
            // As a workaround get them from the elements array instead.
            // In any case it is always safe to get the element from the
            // elements array.
            //var hiddenField = form[paramName];
            var hiddenField = form.elements[paramName];
            if (_agent.isPIE)
            {
              hiddenField.value = paramValue;
            }
            else
            {
              var hiddenFieldCreated = false;
              // See if the hidden field exists.  And, because
              // of some rather strange IE behavior w/regards to
              // form.elements['id'], make sure we haven't accidentally
              // grabbed a string
              if (hiddenField && (typeof(hiddenField) != "string"))
              {
                // This condition was added to support enter key
                // on forms for hcommandButton
                if (hiddenField.type == 'submit' || hiddenField.type == 'button')
                {
                  var tmpField = document.createElement("input");
                  tmpField.type = "hidden";
                  tmpField.name = paramName;
                  tmpField.value = parameters[paramName];
                  form.appendChild(tmpField);
                  tempParams[paramName] = tmpField;
                  hiddenFieldCreated = true;
                }
                else
                {
                  hiddenField.value = paramValue;
                }
              }
              //VAC- added so that PDA's do not enter this flow. Since no PDA currently
              //supports createElement function on the document.  Furthermore, if the
              //hidden field exists there should be no reason to create a new hidden field
              //with the same name and attach it to the form.
              else
              {
                if (isDOM)
                {
                  if (! hiddenFieldCreated)
                  {
                    // as a convenience to the client, build a hidden field to hold
                    // this parameter.
                    var tmpField = document.createElement("input");
                    tmpField.type = "hidden";
                    tmpField.name = paramName;
                    tmpField.value = parameters[paramName];
                    form.appendChild(tmpField);
                    tempParams[paramName] = tmpField;
                  }
                }
              }
            }
          }
        }
      }
      // IE BUG, see TRINIDAD-704
      if(_agent.isIE)
        _autoCompleteForm(form);

      try
      {
        form.submit();
      }
      catch (e)
      {
        if (TrPage.getInstance().getRequestQueue()._isMultipartForm(form))
        {
          // IE will fail on an input file submission of a file that does not exist
          var facesMessage = _createFacesMessage(
            'org.apache.myfaces.trinidad.component.core.input.CoreInputFile.INPUT_FILE_ERROR');
          // if there's nowhere to display the message in either
          // summary or detail, then pop an alert to warn the page developer
          if (!TrMessageBox.isPresent())
            alert(facesMessage.getDetail());
          else
            // Add the message to the MessageBox
            TrMessageBox.addMessage(null, null, facesMessage);
        }
        else
        {
          throw e;
        }
      }

      if (_blockOnEverySubmit)
        _pprStartBlocking(window);


      // Remove any dynamically added form parameters. We do this for two
      // reasons:
      // 1. IE6 does not return dynamically-added form elements in the form map,
      // so we end up re-adding the same form elements again.
      // 2. If we don't remove them, then subsequent form submits behave like
      // they are PPR requests (because the dynamically added "partial" and
      // "partialTargets" parameters will be on the request).
      // (Bug #3623890. This seems to break on a few Apps pages with bad form
      // setups)
      if (isDOM)
      {
        for (var paramName in tempParams)
          form.removeChild(tempParams[paramName]);
      }
    }
  }

  return doSubmit;
}

/**
 * Internet Explorer has a bug, that the autocomplete does not work when
 * using JavaScript to submit a form.
 */
function _autoCompleteForm(form)
{
  var theExternal = window.external;

  if (theExternal && (typeof theExternal.AutoCompleteSaveForm != "undefined"))
  {
    try
    {
      theExternal.AutoCompleteSaveForm(form);
    }
    catch (e)
    {
      // ignore
    }
  }
}

/**
 * This function is called when enter key is hit on any form input element.
 * @src if non-null, the ID of the object to fire
 */
function _submitOnEnter(e, frm, src, immediate, ppr)
{
  if (window.event != null)
    e = window.event;

  var eventSource;
  if (e.srcElement == undefined)
    // Gecko browsers
    eventSource = e.target;
  else
    eventSource = e.srcElement;

  if (!eventSource) return true;
  // Only process for "INPUT": but not for submit and reset
  // buttons
  if(eventSource.tagName == 'A') return true;

  if ((eventSource.tagName == 'INPUT') &&
      (eventSource.type != 'submit') &&
      (eventSource.type != 'reset'))
  {
    if (_getKC(e)==13)
    {
      if (src != null)
      {
        var params = new Object();
        params[src] = src;
        params['source'] = src;

        if(ppr != true)
        {
          submitForm(frm,immediate,params);
        }
        else
        {
          TrPage._autoSubmit(frm, src, e, immediate, params);
        }
      }

      return false;
    }
  }

  return true;
}

/**
 * In some cases we need to hold off on a submit for a while (waiting for the
 * page to complete rendering, waiting for another submit to complete, etc.).
 * This function will save off the state of the submit request for later
 * processing in _submitFormCheck().
 */
function _saveFormForLaterSubmit(form, val, params)
{
  // TODO: fix for PPR
  _saveForm = form;
  _saveDoValidate = val;
  _saveParameters = params;
  _submitRejected = true;
}

/**
 * Checks if _submitForm had been called before the form had completely
 * rendered, and if so, recall it. This function is rendered at the end of the
 * form, so it is guaranteed that the form is complete when this is called.
 */
function _submitFormCheck()
{
  if (_submitRejected)
  {
    if (_inPartialSubmit)
    {
      _submitPartialChange(_saveForm, _saveDoValidate, _saveParameters);
      _inPartialSubmit = false;
    }
    else
    {
      submitForm(_saveForm, _saveDoValidate, _saveParameters);
    }
    _saveForm = null;
    _saveDoValidate = null;
    _saveParameters = null;
  }
}

/**
 * Attempts to reset the form, calling
 * any reset function calls registered on the form.
 * The form will be reloaded if any
 * reset function call returns <code>true</code>.
 * This function returns <code>true</code> if the page
 * had to be reloaded, and false otherwise.
 * <p>
 * @param form The form to submit.  This can either be the name of the form
 *             in the current <code>document</code>, the index of the form
 *             in the current <code>document</code> or the form itself.
 */
function resetForm(
  form
  )
{
  var doReload = false;

  // if the form was passed as a form name, get the form object
  if ((typeof form) == "string")
  {
    form = document[form];
  }
  // if the form was passed as a form index, get the form object
  else if ((typeof form) == "number")
  {
    form = document.forms[form];
  }

  // we had better have a form now
  if (!form)
    return false;

  var doReload = TrPage.getInstance()._resetForm(form);
  if ( doReload )
  {
    window.document.location.reload();
  }
  else
  {
    form.reset();
  }

  _lastDateReset = new Date();
  return doReload;
}


// Create  query string with the data from a given form
function createNameValueString(form) {
  var datatosend = "";
  try
  {
    var arr = form.elements;
    for (var i = 0; i < arr.length; i++)
    {
      try
      {
        var element = arr[i];
        if(element.name)
        {
          if (element.type == "text"
              || element.type == "password"
              || element.type == "textarea"
              || element.type == "hidden")
          {
            datatosend += (element.name + "=" + escape(element.value) + "&");
          }
          else if (element.type.indexOf("select") != -1)
          {
            //PH:selectdata must be initialized to "". Otherwise, results for
            //selectdata+="stringtoconcatenate" is "undefinedstringtoconcatenate"
            var selectdata ="" ;
            for (var j = 0; j < element.options.length; j++)
            {
              if (element.options[j].selected == true)
                selectdata += element.name + "="
                              + escape(element.options[j].value) + "&";
            }
            if( !selectdata)
            {
              var val = _getValue(element);
              if (val)
              {
                selectdata += element.name + "=" + escape(val) + "&";
              }
            }
            if (selectdata)
            {
              datatosend += selectdata;
            }
          }
          else if (element.type == "checkbox" && element.checked)
            datatosend += ( element.name + "=" + escape(element.value) + "&");
          else if (element.type == "radio" && element.checked == true)
            datatosend +=  (element.name + "=" + escape(element.value) + "&");
        }
      }
      catch (e)
      {
      }
      element = null;
    }
  }
  catch(e)
  {
  }
  return ( datatosend.substring(0, datatosend.length - 1));
}



/**
 * Returns the value of a form element.
 */
function _getValue(formElement)
{
  var shadowElem = formElement;
  var elementType = formElement.type;

  // When we're dealing with an array of elements, find the
  // real element type by looking inside the array.
  if (!elementType && formElement.length)
  {
    // See bug 3651045;  IE can put "fieldsets" in with
    // form elements!
    for (var i = 0; i < formElement.length; i++)
    {
      elementType = formElement[i].type;
      if (elementType != (void 0))
      {
        shadowElem = formElement[i];
        break;
      }
    }
  }

  if (elementType == "checkbox")
  {
    if (formElement.length)
    {
      for (var i = 0; i < formElement.length; i++)
      {
        // See above for why we check each element's type
        if (formElement[i].type == "checkbox" &&
            formElement[i].checked)
        {
          return formElement[i].value;
        }
      }
    }
    else
    {
      return formElement.checked;
    }
  }
  else if (elementType == "select-multiple")
  {
    var multiResult = new Array();
    for (var i = 0; i < formElement.length; i++)
    {
      if(formElement.options[i].selected)
      {
        multiResult[multiResult.length] = formElement.options[i].value;
      }
    }
    return (multiResult.length > 0) ? multiResult : "";
  }
  else if (elementType.substring(0,6) == "select")
  {
    formElement = shadowElem;
    var selectedIndex = formElement.selectedIndex;

    // selectedIndex exists and non-negative
    if (selectedIndex != (void 0) &&
        selectedIndex != null &&
        selectedIndex >= 0)
    {
      var opt = formElement.options[selectedIndex];
      var value = opt.value;
      if (!value)
      {
        // If there's no value, it could be for two reasons:
        //  (1) The user has only specified "text".
        //  (2) The user explicitly wanted "value" to be empty.
        // We can't really tell the difference between the two,
        // unless we assume that users will be consistent with
        // all options of a choice.  So, if _any_ option
        // has a value, assume this one was possibility (2)
        for (var i = 0; i < formElement.options.length; i++)
        {
          if (formElement.options[i].value)
            return value;
        }

        // OK, none had a value set - this is option (1) - default
        // the "value" to the "text"
        return opt.text;
      }

      return value;
    }

    // no selected value
    return "";
  }
  else if (elementType == "radio")
  {
    if (formElement.length)
    {
      for (var i = 0; i < formElement.length; i++)
      {
        // See above for why we check each element's type
        if (formElement[i].type == "radio" &&
            formElement[i].checked)
        {
          return formElement[i].value;
        }
      }
    }
    else
    {
      if (formElement.checked)
      {
        return formElement.value;
      }
    }

    // no selected value
    return "";
  }
  else
  {
    return formElement.value;
  }
}

/**
 * Sets the selected index
 */
function _setSelectIndexById(id, index)
{
  var element = _getElementById(document, id);
  if (element != null)
    element.selectedIndex = index;
}


/**
 * Synchronizes the index of a repeated choice.
 */
function _syncChoiceIndex(ch)
{
  var form = ch.form;
  var name = ch.name;
  var comps = form.elements[name];
  for (i=0; i<comps.length; i++)
  {
    comps[i].selectedIndex = ch.selectedIndex;
  }
}


/**
 * Clears a password field if it contains the magic postback string.
 */
function _clearPassword(field, e)
{
  if (window.event != (void 0))
    e = window.event;

  if (field.value != "******")
    return true;

  // Backspace
  if ((e.keyCode == 8) ||
     // Delete (46) through F1 (112)
      ((e.keyCode >= 46) && (e.keyCode < 112)))
    field.value="";
  return true;
}


/**
 * If appropriate sets the focus on the input passed in
 */
function _setFocus(currInput)
{
  // check if currInput is showing before setting focus, for example
  // ColorField has required validation on hidden field,
  // but cannot receive focus.
  if (_isShowing(currInput))
  {
    if (currInput.focus)
      currInput.focus();

    //PH:element["value"] is not supported for PIE,IEM and BB. Therefore
    //use element.value which is supported by all
    if ((currInput.type == "text")
        && (currInput.value != (void 0))
        && (currInput.value != null)
        && (currInput.value.length > 0))
    {
      // IE fails on this select if a timeout occurs to handle a
      // pending event. Don't do it if we've reset the delayed
      // events object.
      if (true != _delayedEventParams["reset"])
        currInput.select();
    }
  }
}

function _addValidators(formName, validators, validations, labels, formats)
{
  var form = document.forms[formName];
  var validatorMap = _getValidators(form);
  if (!validatorMap)
    validatorMap = new Object();

  // Now, iterate through the array we've been given
  for (var i = 0; i < validators.length; i += 5)
  {
    var id = validators[i];
    var descriptor = new Object();

    // If the field is required, replace the format index with the
    // actual message
    if (validators[i + 1])
    {
      var formatIndex = validators[i + 2];
      descriptor.required = true;
      descriptor.requiredFormat = formats[formatIndex];
    }

    // If the converter exists, change it from an index to a converter
    var converterIndex = validators[i + 3];
    if (converterIndex != null)
    {
      descriptor.converter = validations[converterIndex];
    }

    // If there's a validator array, reuse it after converting
    // the indices to validator objects
    var validatorArray = validators[i + 4];
    if (validatorArray)
    {
      for (j = 0; j < validatorArray.length; j++)
      {
        validatorArray[j] = validations[validatorArray[j]];
      }

      descriptor.validators = validatorArray;
    }

    // Store the label on the descriptor
    var label = labels[id];
    if (label)
      descriptor.label = label;

    // Stash the descriptor on the validator map
    validatorMap[id] = descriptor;

    // If enabled, setup event based validation
    if (_TrEventBasedValidation)
    {
      var inputElem = _getElementById(document, id);
      if (inputElem)
      {
        _addEvent(inputElem, "change", _validateInput);
      }
    }
  }

  // And store the new validator map away
  window["_" + _getJavascriptId(_getFormName(form)) + "_Validators"] = validatorMap;
}

/**
 * Calls an array of validation functions and returns a map of validation
 * errors.  Each map entry is keyed by an id of an input component
 * and contains an array of TrFacesMessage objects relating to the
 * component (i.e. <String, TrFacesMessage[]>).
 */
function _multiValidate(
  form,
  source,
  validators
  )
{
  // Initialise the return map.
  var failureMap = new Object();

  var subforms = window[_getFormName(form) + "_SF"];
  var ignorePrefixes = new Array();
  var foundUsedSubform = false;
  var key;
  if (source != (void 0))
  {
    // Find if there's any prefix that matches
    for (key in subforms)
    {
      if (source.indexOf(key + ":") == 0)
      {
        foundUsedSubform = true;
        break;
      }
    }

    // Build up all prefixes that don't match
    for (key in subforms)
    {
      if (source.indexOf(key + ":") != 0)
      {
        if ((foundUsedSubform) || (subforms[key] == 1))
          ignorePrefixes.push(key + ":");
      }
    }
  }

  // We check for any relevent validation failures here, not just validations.
  // If a validation has been run on one field in the form (e.g. an onBlur), we
  // still need to run every other validation. However, if that one validation
  // failed, the user has seen one alert, don't bug them with a second til they
  // have fixed the first error.
  if (validators && !_recentValidation(true))
  {
    for (var id in validators)
    {
      if(_getElementById(document, id) == null)
      {
          continue;
      }

      var isIgnored = false;
      // If this field is one that's specifically being ignored,
      // then don't validate here.
      for (var j = 0; j < ignorePrefixes.length; j++)
      {
        if (id.indexOf(ignorePrefixes[j]) == 0)
        {
          isIgnored = true;
          break;
        }
      }

      if (isIgnored)
        continue;

      // get the current form element to validate
      var currInput = _getFormElement(form, id);

      // Make sure we have a non-null input control.  It is possible
      // that in rich client environments the DOM for the input
      // control may have been temporarily removed from the document.
      // If we don't find DOM for the current input, move on to the
      // next input.

      // todo: Should also check for visibility of currInput, since
      //       rich client may have "hidden" the input, in which case
      //       validation shouldn't fire.
      if (!currInput)
        continue;

      //Initialize the failure array for this input
      var inputFailures = new Array();

      var descriptor = validators[id];
      var label = descriptor.label;

      // if currInput is an array then multiple elements have the same name.
      // Only the first will be validated as subsequent values should be in sync
      var elementType = currInput.type;

      if (!elementType && currInput.length)
      {
        var firstType = currInput[0].type;
        if (firstType != "radio" && firstType != "checkbox")
        {
          currInput = currInput[0];
        }
      }

      var value = _getValue(currInput);
      var required = descriptor.required;
      if ( required && ((value == "" ) || (value == null)))
      {

        // get the formatted error string for the current input and
        var requiredErrorString = _getErrorString(currInput, label,
                                                  descriptor.requiredFormat);

        // Populate the failureMap with the current error
        inputFailures[inputFailures.length] =
            new TrFacesMessage(requiredErrorString, requiredErrorString);
      }
      else
      {
        var converterConstructor = descriptor.converter;

        // set the converterError var to false for each input, otherwise nothing
        // after the first conversion error is validated
        var converterError = false;

        if ( converterConstructor )
        {

          // do the conversion if this element has a value
          if ((value != null) &&
              !((typeof value == "string") && (value == "")))
          {
            var converter = eval(converterConstructor);
            try
            {
              value = converter.getAsObject(value, label);
            }
            catch (e)
            {
              converterError = true;
              // Populate the failureMap with the current error
             if (_agent.isPIE || _agent.isNokiaPhone || _agent.isBlackBerry)
             {
               inputFailures[inputFailures.length] = e.message;
             }
             else
             {
               inputFailures[inputFailures.length] = e.getFacesMessage();
             }
            }
          }
        }

        if ( converterError == false)
        {
          var validatorArray = descriptor.validators;
          if (validatorArray)
          {
            for ( var j = 0; j < validatorArray.length; j = j + 1)
            {
              // do the validation if this element has a value
              // Don't just compare against "", since the value has
              // already been converted to a non-string type
              if ((value !== null) &&
                  !((typeof value == "string") && value == ""))
              {
                // evaluate the validator
                var validatorConstructor = validatorArray[j];
                if (validatorConstructor && value !== undefined)
                {
                  var validator = eval(validatorConstructor);

                  try
                  {
                    validator.validate(value, label, converter);
                  }
                  catch (e)
                  {
                    // Populate the failureMap with the current error
                    if (_agent.isPIE || _agent.isNokiaPhone || _agent.isBlackBerry)
                    {
                      inputFailures[inputFailures.length] = e.message;
                    }
                    else
                    {
                      inputFailures[inputFailures.length] = e.getFacesMessage();
                    }
                  }
                }
              }
            }
          }
        }
      }

      // if there were failures, then add the current input to the failuresMap
      if (inputFailures.length > 0)
      {
        // TRINIDAD-123: Use input 'name' from validators array rather than currInput.id
        // to avoid issues with radio buttons having numeric id suffixes
        failureMap[id] = inputFailures;
      }
    }
  }

  return failureMap;
}

/**
 * Used for the converters and validators we provide which all have the form
 *
 * {0} - label
 * {1} - string value
 * {2} - extra param
 * {3} - extra param
 */
function _createFacesMessage(
  key,
  label,
  value,
  param2,
  param3
)
{
  var summary = TrMessageFactory.getSummaryString(key);
  var detail = TrMessageFactory.getDetailString(key);
  // format the detail error string
  if (detail != null)
  {
    detail = TrFastMessageFormatUtils.format(detail, label, value, param2, param3);
  }
  return new TrFacesMessage(summary,
                          detail,
                          TrFacesMessage.SEVERITY_ERROR);
}

/**
 * Used for the converters and validators we provide which all have the form
 *
 * {0} - label
 * {1} - string value
 * {2} - extra param
 * {3} - extra param
 */
function _createCustomFacesMessage(
  summary,
  detail,
  label,
  value,
  param2,
  param3
)
{

  // format the detail error string
  if (detail != null)
  {
    detail = TrFastMessageFormatUtils.format(detail, label, value, param2, param3);
  }

  return new TrFacesMessage(summary,
                          detail,
                          TrFacesMessage.SEVERITY_ERROR);
}


function _getGlobalErrorString(
  input,
  errorFormat,
  errorString,
  label
  )
{
  var form = _getForm(input);
  if (errorFormat && label != null)
  {
    return _formatErrorString(errorFormat,
                             {
                               "0":label,
                               "1":errorString
                             });
  }

  return errorString;
}


/**
 * Returns true if the element is visible such that it could
 * receive focus or have its value selected, otherwise false.
 */
 function _isShowing(
   input)
 {
   //PH: removed !input.focus because firstly, focus() function is supported by
   //all browsers (PIE,IEM,BB,FF,IE) and secondly, _isShowing should be treated
   //as a function to test visibility only. If there is a case where one really
   //wants to test whether focus function exists or not, do it in an if
   //statement and call _isShowing within it.
   if (input.type == 'hidden')
       return false;

   // determine visibility from style information
   if (_agent.isIEGroup)
   {
     var node = input;

     // IE does not give a "computed" style, so we
     // need to walk up the DOM to get the styles
     while (node != (void 0))
     {
       computedStyle = node.currentStyle;

       if ((computedStyle != (void 0)) &&
           ( (computedStyle["visibility"] == "hidden") ||
             (computedStyle["display"] == "none")))
       {
         // node or one of its parents parents are NOT showing
         return false;
       }

       // consider parent style
       node = node.parentNode;
     }

     // node and all parents are showing
     return true;
   }

   if (_agent.isGecko || _agent.isSafari || _agent.BlackBerry)
   {
     // Radio buttons:  it'll be an array
     if (!input.ownerDocument && input.length)
       input = input[0];

     var computedStyle = input.ownerDocument.defaultView.getComputedStyle(input,
                                                                          null);

     // either of these styles will prevent focus from succeeding
     return ((computedStyle["visibility"] != "hidden") &&
             (computedStyle["display"] != "none"));
   }

   return true;
 }

/**
 * Returns the id of an input element on either IE or Netscape, dealing
 * with the fact that Netscape doesn't support IDs locally.
 */
 function _getID(
   input
   )
 {
   //VAC- bug 4205372 for PIE devices return the name of the input element
   if (_agent.isPIE)
   {
     return input.name;
   }

   // for non-Netscape return the ID directly
   var id = input.id;

   var inputType = input.type;

   if (!inputType && input.length)
     inputType = input[0].type;

   // for radio buttons, return ID of enclosing <span>
   if (inputType == "radio")
   {
     var inputParent;
     if (input.length)
     {
       inputParent = input[0].parentNode;
       if (inputParent.tagName == 'FIELDSET')
         inputParent = inputParent.parentNode;
     }
     else
     {
       inputParent = input.parentNode;
     }

     id = inputParent.id;
   }

   return id;
 }


/**
 * Returns the form of an input element on either IE or Netscape, dealing
 * with the fact that radio inputs do not directly support the form attribute.
 */
 function _getForm(
   input
   )
 {
   var form = input.form;

   if (form == (void 0))
   {
     // Try the first item of the array
     if (input.length)
     {
       form = input[0].form;
     }
   }

   return form;
 }

/**
 * Returns the element of name elementName for the given form
 */
 function _getFormElement(
   form,
   elementName)
{
  var formElement = null;
  if (_agent.isPIE)
  {
      formElement = form.elements[elementName];
  }
  else
  {
    formElement = form[elementName];
    // To support required validation on shuttle component
    if(formElement == undefined)
    {
      formElement = form.elements[elementName+":trailing:items"];
    }
  }
  return formElement;
}


/**
 * Returns the name of an input element on either IE or Netscape, dealing
 * with the fact that radio inputs do not directly support the name attribute.
 */
 function _getName(
   input
   )
 {
   var name = input.name;

   if (name == (void 0))
   {
     var inputType = input.type;

     if (!inputType && input.length)
       inputType = input[0].type;

     // for radio buttons, return ID of enclosing <span>
     if (inputType == "radio" && input.length)
     {
       name = input[0].name;
     }
   }

   return name;
 }

/**
 * Return true if the object or any of its prototypes'
 * are an instance of the specified object type.
 */
function _instanceof(
  obj,  // the object instance
  type  // the constructor function
)
{
  if (type == (void 0))
    return false;

  if (obj == (void 0))
    return false;

  while (typeof(obj) == "object")
  {
    if (obj.constructor == type)
      return true;

    // walk up the prototype hierarchy
    obj = obj.prototype;
  }

  return false;
}



/**
 * Return the formatted error string for an input field
 * and an errorFormatIndex
 */
function _getErrorString(
  input,
  label,
  defaultErrorFormat,
  validationError
  )
{
  var errorFormat;

  var form = _getForm(input);
  var value = _getValue(input);

  // use the message embedded in the validationError, if any
  if (_instanceof(validationError, window["TrConverterException"]))
  {
    errorFormat = validationError.getFacesMessage().getDetail();
  }
  // use the message embedded in the validationError, if any
  else if (_instanceof(validationError, window["TrValidatorException"]))
  {
    errorFormat = validationError.getFacesMessage().getDetail();
  }
  else
  {
    errorFormat = defaultErrorFormat;
  }

  if (errorFormat)
  {
    // format the error string, replacing the following tokens
    //   {0}    the value of the label
    //   {1}    the value of the input element
    var errorString = _formatErrorString(errorFormat,
                                         {
                                           "0":label,
                                           "1":value
                                         });
    // return the error
    return errorString;
  }
}




/**
 * Returns the array of form validators.
 */
function _getValidators(
  form
  )
{
  return window["_" + _getJavascriptId(_getFormName(form)) + "_Validators"];
}



/**
 * Performs token replacement on the the error format, replacing each
 * token found in the token Object with the value for that token.
 */
function _formatErrorString(
  errorFormat, // error format string with embedded tokens to be replaced
  tokens       // tokens Object containin token names and values to be replaced
  )
{
  var currString = errorFormat;

  // loop through all of the tokens, replacing them if they are present
  for (var currToken in tokens)
  {
    var currValue = tokens[currToken];

    // if the token has no value
    if (!currValue)
    {
      currValue = "";
    }

    // TRINIDAD-829:
    // we replace '{' and '}' to ensure, that tokens containing values
    // like {3} aren't parsed more than once...
    // Only do this if it is typeof string (see TRINIDAD-873)
    if (typeof currValue == "string")
    {
    currValue = currValue.replace("{","{'");
    currValue = currValue.replace("}","'}");
    }

    // the tokens are delimited by '{' before and '}' after the token
    var currRegExp = "{" + currToken + "}";

    // support tokens of the form %token% as well as {token}
    currString = currString.replace(new RegExp('%' + currToken + '%', 'g'),
                                    currRegExp);

    // Replace the token.  Don't use String.replace, as the value may
    // include dollar signs, which leads Netscape astray (bug 2242675)
    var indexOf = currString.indexOf(currRegExp);

    if (currValue.indexOf && currValue.indexOf(currRegExp) >= 0)
    {
     var b1 = '';
     for (i=0; i<currValue.length; i++)
     {
       b1 = b1 + 'placeHolderString';
     }

     while (indexOf >= 0)
    {
      currString=(currString.substring(0,indexOf)
           + b1
           + currString.substring(indexOf+currRegExp.length));
      indexOf = currString.indexOf(currRegExp);
    }

    indexOf = currString.indexOf(b1);

    while (indexOf >= 0)
    {
      currString =(currString.substring(0,indexOf)
           + currValue
           + currString.substring(indexOf+b1.length));
      indexOf = currString.indexOf(b1);
    }
  }
  else
    while (indexOf >= 0)
    {
      currString = (currString.substring(0, indexOf)
                      + currValue
                      + currString.substring(indexOf + currRegExp.length));
      indexOf = currString.indexOf(currRegExp);
    }
 }

  // TRINIDAD-829:
  // we finally re-replace the '{' and '}'...
  while(currString.indexOf("{'")!=-1)
  {
    currString= currString.replace("{'","{");
    currString= currString.replace("'}","}");
  }

  // And now take any doubled-up single quotes down to one,
  // to handle escaping
  var twoSingleQuotes = /''/g;

  return currString.replace(twoSingleQuotes, "'");
}


/**
 * Chain two functions together returning whether the default
 * event handling should occur
 */
function _chain(
  evh1,        // event handler 1 string
  evh2,        // event handler 2 string
  target,      // target of event
  event,       // the fired event
  shortCircuit // shortcircuit if handler 1 false
  )
{
  var result1 = _callChained(evh1, target, event);
  if ( shortCircuit && (result1 == false))
    return false;
  var result2 = _callChained(evh2, target, event);

  // since undefined results should be evaluated as true,
  // return false only if either result1 or result2 return false
  return !((result1 == false) || (result2 == false));
}

function _callChained(
  handler,
  target,
  event
  )
{

  if (handler && (handler.length > 0))
  {
    // handle ie case, where we have no event parameter

    if( (typeof(event) == 'undefined') || (event == (void 0) ) )
    {
      event = window.event;
    }

    // create function so that "return" is handled correctly,
    // use event parameter so that both ie and netscape
    // functions work
    var func = new Function("event", handler);
    var result;

    // WindowsMobile 5 doesn't support installing Funtion object
    // to "this", so just invoke the Function object instead.
    if (_agent.isPIE)
    {
      result = func(event);
    }
    else
    {
      // install the function on the object so that "this" is
      // handled correctly
      target._tempFunc = func;

      // evaluate the result
      result = target._tempFunc(event);

      // clear the temporary function
      target._tempFunc = (void 0);
    }

    // undefined results should be evaluated as true,
    return !(result == false);
  }
  else
  {
    return true;
  }

}



// Enforce the maximum length of a form element
// Returns true if event processing should continue, false otherwise.
function _checkLength(formElement, length, event)
{
  elementLength = formElement.value.length;
  if (elementLength > length)
  {
    // Input is longer than max, truncate and return false.
    // This takes care of the case where the user has pasted in text
    // that's too long. Return true here because the onChange event can
    // continue (now that we've truncated the value). This allows chained
    // handlers to work.
    formElement.value = formElement.value.substr(0, length);
    return true;
  }

  // If less than max length (i.e. within acceptable range), return true
  if (elementLength < length)
    return true;

  // If we've made it to here, we know that elementLength == length

  if (_agent.isIE)
  {
    // in many forms there is a hidden field named "event"
    // Sometimes IE gets confused and sends us that instead of
    // the true event, so...
    if (event["type"] == "hidden")
      event = window.event;
  }

  // If this is a change event, the field has already been updated to a string
  // of the maximum allowable length. This is fine. Continue processing.
  if (event.type == 'change')
    return true;

  // If we've made it to here, we know that this is a keyPress event

  // If the input is something less than a space (e.g. tab, CR, etc.)
  // return true.
  // If key was CTRL-v, which will be used to paste some new text,
  // pass it along.
  if (event)
  {
    if ((event.which < 32)
        || ((event.which == 118) && (event["ctrlKey"])))
      return true;
  }

  // Default return FALSE. If we're here, this is an onKeyPress event, it's a
  // printable character, and elementLength already equals the maximum allowed.
  // We need to return false here to cancel the event otherwise this last
  // character will end up in the input field in position MAX+1.
  return false;
}

/**
 * Cover for document.getElementById that works on IE 4.x
 */
function _getElementById(
  doc,
  id
  )
{
  //PH: Since BB supports getDocumentById use this to obtain the element.
  if(typeof(doc.getElementById) != 'undefined')
  {
    //
    // If we arent' on Internet Explorers before 5,
    // use the DOM way of doing this
    //
    //PH:exclude BlackBerry
    if (((_agent.kind != "ie") || (_agent.version >= 5)) && (!_agent.isBlackBerry))
    {
      var element = doc.getElementById(id);

      // IE's implementation of getElementById() is buggy.  If
      // the page contains an anchor which has the same name
      // as the requested id, IE will return the anchor, even
      // if the anchor's id attribute is not set.  So, make
      // sure that we actually get back an element with the
      // correct id.
      if ((element == null) || (element.id == id))
        return element;
      // If we get here, that means that IE has probably returned
      // an anchor instead of the desired element.  Let's scan
      // the entire DOM tree to find the element we want.
      return _findElementById(doc, id);
    }

    return doc.getElementById(id);
  }
  else if (typeof(doc.all) == 'undefined')
  {
    // Browser does not support getElementById nor DOM documnet.all object.
    // One example of such browser today is BlackBerry 4.0 browser.

    //if element is not within a form
    if(doc.forms.length == 0)
      return window[id];
    else
      //check to see if element is within the form, if so return the element else do nothing
      for(var i = 0; i<doc.forms.length; i++)
      {
        var f = doc.forms[i];
        if(f[id])
          return f[id];
      }

    //element is not within the form but form(s) is(are) present.
    return window[id];
  }
  else
  {
    // Browser does not support getElementById but supports DOM documnet.all
    // object. One example of such browser today is Windows Mobile browser.
    return doc.all[id];
  }
}

// A recursive method which searches the entire DOM tree
// to find the element with the specified ID
function _findElementById(
  node,
  id
  )
{
  // Check to see if the current node is the node
  // that we are looking for
  if (node.id == id)
    return node;

  // Check all children of the current node
  if (node.childNodes)
  {
    var childNodes = node.childNodes;
    for (var i = 0; i < childNodes.length; i++)
    {
      var foundNode = _findElementById(childNodes.item(i), id);
      if (foundNode != null)
        return foundNode;
    }
  }

  return null;
}

// Returns '?' or '&' depending on whether the
// baseURL already contains a query string
function _getQuerySeparator(baseURL)
{
  var lastChar = baseURL.charAt(baseURL.length - 1);
  if ((lastChar == '&') || (lastChar == '?'))
    return "";

  return (baseURL.indexOf('?') >= 0) ? '&' : '?';
}

/**
 * Adds a parameter to an existing URL, replacing the parameter if
 * it already exists
 */
function _addParameter(
  baseURL,
  paramName,
  paramValue
  )
{
  // check if we have parameters
  var queryIndex = baseURL.indexOf('?');

  if (queryIndex == -1)
  {
    // no parameters, so append to parameters
    return baseURL + '?' + paramName + '=' + paramValue;
  }
  else
  {
    // check if the parameter already exists
    var paramIndex = baseURL.indexOf('?' + paramName + '=', queryIndex);

    if (paramIndex == -1)
      paramIndex = baseURL.indexOf('&' + paramName + '=', queryIndex + 1);

    if (paramIndex == -1)
    {
      // parameter isn't in the URL
      return baseURL + '&' + paramName + '=' + paramValue;
    }
    else
    {
      //
      // replace the value of the parameter
      //
      // the +2 skips over the '&' or '?' and the '='
      var valueIndex = paramIndex + paramName.length + 2;

      // get the URL + the parameter
      var newString = baseURL.substring(0, valueIndex);

      // append the new value
      newString += paramValue;

      var lastIndex = baseURL.indexOf('&', valueIndex);

      if (lastIndex != -1)
      {
        // append the rest of the string after the replaced value
        newString += baseURL.substring(lastIndex);
      }

      return newString;
    }
  }
}

/**
 * Adds a parameter to the parameters object for form submission
 */
function _addFormParameter(
  parameters,
  paramName,
  paramValue
  )
{
  // Always create a new object, since we don't want to mess with
  // the caller's parameters
  var newParameters = new Object();

  // Copy over existing parameters
  if (parameters)
  {
    for (var name in parameters)
      newParameters[name] = parameters[name];
  }

  // Now set the new parameter value
  newParameters[paramName] = paramValue;

  return newParameters;
}

//
// _pprInstallBlockingHandlers: Helps implement blocking
//                              This function just installs or de-installs the
//                              event consuming handlers.
//
function _pprInstallBlockingHandlers(win, install)
{
  var doc = win.document;

  if (doc == (void 0))
    return;

  // Some mobile browser do not support attaching event listner
  // to document
  if (!doc.attachEvent && !doc.addEventListener)
  {
    return;
  }

  if (doc.attachEvent) // IE
  {
    var el = win._pprConsumeFirstClick;
    if (install)
    {
      // See comment in _pprConsumeFirstClick().
      // If the event that started this PPR chain was an onChange or onBlur,
      // AND the event location is the element on which the change happened
      // (i.e. the user didn't click somewhere outside the element)
      // then we want to make sure that the blocking starts immediately.
      var ev = win.event;
      if (ev != (void 0))
      {
        var destElt = document.elementFromPoint(ev.x, ev.y);
        if (!win._pprFirstClickPass // never attach unless passing first click
            || (((ev.type == 'change') || (ev.type == 'blur'))
                && (ev.srcElement == destElt))
            || (!_isSubmittingElement(destElt)))
        {
          _pprControlCapture(win, true);
          return;
        }
      }

      // If we're here, we didn't set up a capture.
      // For an onClick, we have to pass on the first click,
      // then we'll capture every subsequent event.
      doc.attachEvent('onclick', el);
    }
    else
    {
      doc.detachEvent('onclick', el);
      _pprControlCapture(win, false);
    }
  }
  else // Gecko or other standards based browser
  {
    var el = win._pprConsumeBlockedEvent;

    // Set up the same handler on all these events. The handler will just eat
    // the event unless it's the first click and we're passing that.
    var handlers = { 'click':1, 'keyup':1, 'keydown':1, 'keypress':1};
    for (var h in handlers)
    {
      if (install)
        doc.addEventListener(h, el, true);
      else
        doc.removeEventListener(h, el, true);
    }
  }
}

//
// _pprConsumeClick: Helps implement blocking. This function just consumes
//                   every click that falls within the body.
//
function _pprConsumeClick(event)
{
  if (_agent.isIE)
  {
    var body = document.body;
    if ((event.x < body.offsetLeft) || (event.y < body.offsetTop)
        || (event.x > body.offsetWidth) || (event.y > body.offsetHeight))
    {
      // OK, we've caught an event outside the body of the document. Assume
      // that the user is clicking somewhere on the menu bar, or another
      // window. At this point, we release the mouse and continue (that's
      // better than keeping the user in limbo).

      _pprStopBlocking(window);
    }
  }
  return false;
}


//
// _pprStartBlocking: Starts consuming every click (to implement blocking)
//
function _pprStartBlocking(win)
{
  // No blocking is performed on WM, Nokia, PPC and BlackBerry devices
  if (_agent.isPIE || _agent.isNokiaPhone || _agent.isBlackBerry)
    return;

  if (_agent.isIE)
  {
    // see TRINIDAD-952 - IE does not update the activeElement in time before
    // blocking starts. Use a timeout to allow the update.
    win._pprTimeoutFunc = win.setTimeout("_doPprStartBlocking(window);",
                                             1);
    return;
  }
  else
  {
     _doPprStartBlocking (win);
  }
}

function _doPprStartBlocking (win)
{
  // Clean up timeout set in _pprStartBlocking()
  if (win._pprTimeoutFunc)
    win.clearTimeout(win._pprTimeoutFunc);

  // In order to force the user to allow a PPR update to complete, we
  // block all mouse clicks between the start of a PPR update, and the end.
  // We do this by building a dummy DIV element and having it grab all clicks.
  // On Mozilla, we just expand it to cover the entire body as a glass frame.
  // On IE, we leave the DIV at zero size, but route every click to it.
  if (!win._pprBlocking)
  {
    var body = win.document.body;
    win._pprBlockStartTime = new Date();

    // XXXSafari: What to do for Safari? Safari will probably work like gecko,
    //            but... need to check.
    if (_agent.isGecko)
    {
      // If the user clicks the stop button, then we'll be stuck blocking.
      // So we don't hang, this timeout will clear the block in eight
      // seconds whether we've finished or not, but first we clear any
      // previously existing timeout.
      if (win._pprBlockingTimeout != null)
      {
        win.clearTimeout(win._pprBlockingTimeout);
      }
      win._pprBlockingTimeout = win.setTimeout("_pprStopBlocking(window);",
                                               8000);
    }
    else if (_agent.isIEGroup)
    {
      // save off the element we'll return focus to
      _pprEventElement = window.document.activeElement;
    }
    _pprInstallBlockingHandlers(win, true);
    win._pprBlocking = true;
  }
}

//
// _pprStopBlocking: Finishes up the blocking, releases the page back
//                   to normal processing
//
function _pprStopBlocking(win)
{

  // No blocking is performed on Nokia, PPC and BlackBerry devices
  if (_agent.isPIE || _agent.isNokiaPhone || _agent.isBlackBerry)
    return;

  var doc = win.document;

  if (win._pprBlocking)
  {
    // XXXSafari: What to do for Safari? Safari will probably work like gecko,
    //            but... need to check.
    if (_agent.isGecko)
    {
      // If we've set a timeout, clear it now.
      if (win._pprBlockingTimeout != null)
      {
        win.clearTimeout(win._pprBlockingTimeout);
        win._pprBlockingTimeout == null;
      }
    }
    // and turn off the event capture
    _pprInstallBlockingHandlers(win, false);

    win._pprEventElement = null;
    win._pprBlocking = false;
  }
  win._pprBlocking = false;
}

/*
 * After updates we often can't just set focus to a node, it has to be prepared
 * in a browser specific way (different idosyncracies cause poor focus
 * behavior).
 */
function _pprFocus(node, doc)
{
  if (_agent.isIEGroup)
  {
    // If the node's parent has changed through a DOM update then
    // this node hasn't been fully added to the tree yet so we
    // can't set focus to it.
    if (node.parentNode == null)
      return;

    // On IE, if a node has focus and we update it, then setting focus to it
    // seems to have no effect. Setting the focus to another node, then back to
    // the target seems to work correctly. Here we set the focus to a hidden
    // field.
    var divnode = _getElementById(doc, _pprdivElementName);
    if ((divnode) && (divnode["focus"]))
      divnode.focus();
  }
  node.focus();
}

//
// _pprConsumeBlockedEvent: Helps implement blocking. This function attached
//                          as the event handler. It just consumes every event
//                          it gets.
//
//                          This function is used on standards based browsers.
//
function _pprConsumeBlockedEvent(evt)
{
  var rv = true;

  if (_pprBlocking)
  {
    var blockTheEvent = true;

    if (window._pprFirstClickPass)
    {
      var newDate = new Date();
      var diff = newDate - _pprBlockStartTime;

      // If we've got a click on a button (or an image within a link that does
      // a submit), less than 150ms after the beginning of a PPR update, assume
      // the user has started an onChange type event with a click on a button.
      // This addresses the problems that people were seeing, but could cause
      // overlapping PPR events in rare cases.
      var delay = 150;
      if ((diff < delay) && (evt.type == 'click'))
      {
        // To try to further limit the overlaps, we only allow clicks on
        // buttons, or images that will cause a submit to go through.
        // get the target of the click
        var orig = evt.explicitOriginalTarget;
        // this function is never called on IE, but if it were, this would be:
        // var orig = (_agent.isIE
        //            ? evt.srcElement
        //            : evt.explicitOriginalTarget);
        blockTheEvent = ! _isSubmittingElement(orig);
      }
    }
    if (blockTheEvent)
    {
      // just swallow the event
      evt.stopPropagation();
      evt.preventDefault();
      rv = false;
    }
  }
  return rv;
}


//
// _pprConsumeFirstClick: Helps implement blocking.
//
// On IE, the capture doesn't allow us to hand off the first click - we can
// only eat it, but attachEvent only allows us to do something with the event
// AFTER it's been delivered to the element. There's no way to make a decision
// whether or not to deliver a particular event. Therefore, since we want to
// deliver the first click, and block everything else, we attachEvent using
// this handler. This handler then just immediately switches over the the
// capture. This function is only used on IE.
//
function _pprConsumeFirstClick(event)
{
  // This is an IE only function
  if (_agent.isIE)
  {
    // switch over to capture
    _pprControlCapture(window, true);
    // and remove this one-time function
    window.document.detachEvent('onclick', _pprConsumeFirstClick);
  }
  return false;
}


//
// _pprControlCapture: Set up the pprDivElement to capture all
//                     mouse events. It will then ignore them.
//
function _pprControlCapture(win, set)
{
  // This is an IE only function
  if (_agent.isIE)
  {
    var doc = win.document;
    var body = doc.body;
    var divElement = _getElementById(doc, _pprdivElementName);
    if (divElement)
    {
      if (set)
      {
        divElement.setCapture();
        // If we've got an element to return focus to,
        // then capture keyboard events also.
        if (win._pprEventElement)
          divElement.focus();
        // save current cursor and display a wait cursor
        win._pprSavedCursor = body.style.cursor;
        body.style.cursor = "wait";
        win._pprSavedCursorFlag = true;
      }
      else if (win._pprSavedCursorFlag)
      {
        divElement.releaseCapture();

        // return focus to the post-PPR target element
        if (win._pprEventElement)
          win._pprEventElement.focus();
        body.style.cursor = win._pprSavedCursor;
        win._pprSavedCursor = null;
        win._pprSavedCursorFlag = false;
      }
    }
  }
  return;
}

// handle the onClick or onBlur for an IE SELECT element
// Returns true if the user has finally made a selection, and is ready to go.
function _pprChoiceAction()
{
  // this function is only needed to handle IE's weird select element
  if (!_agent.isIE)
    return true;

  var rv = false;

  // This gets called as both onClick and onBlur, but both really only want
  // to submit the event if a change has been made.
  if ((!window._pprBlocking) && (_pprChoiceChanged))
  {
    // clear the choice tracker
    _pprChoiceChanged = false;
    rv = true;
  }
  return rv;
}

// handle the onChange for an IE SELECT element
function _pprChoiceChangeEvent(event)
{
  if (!_agent.isIE)
    return true;

  // Just remember the fact that a change has occurred.
  if (!window._pprBlocking)
    _pprChoiceChanged = true;

  return true;
}


// Tests whether a partial submit should be performed
function _supportsPPR()
{
  return !_agent.pprUnsupported;
}


// Fires a PPR request entirely as a GET operation
function _firePartialChange(url)
{
  // FIXME: shouldn't be using a private method on TrPage - this should
  // really be made into a public API on TrPage
  var page = TrPage.getInstance();
  var queue = page.getRequestQueue();
  queue.sendRequest(
    page, page._requestStatusChanged, url);
}

// Fires a partial page request via form submission.
// The args are the same as submitForm().  The
// partialTargets are passed in as parameters
function _submitPartialChange(
  form,
  doValidate,
  parameters)
{
  // If there's no PPR iframe, then just perform a normal,
  // full-page submission.
  if (!_supportsPPR())
    return submitForm(form, doValidate, parameters);

  // Get the actual form object
  if ((typeof form) == "string")
    form = document[form];

  if (!form)
    return false;

  // Tack on the "partial" event parameter parameter
  parameters = _addFormParameter(parameters, "partial", "true");

  // block all mouse clicks until the submit is done
    _pprStartBlocking(window);

  // Submit the form
  var submitted = submitForm(form, doValidate, parameters, true);

  // If the form wasn't actually submitted, update the ref count
  if (!submitted)
  {
    _pprStopBlocking(window);
  }
}

/* If the Trinidad facility needs to set focus to a particular node after a PPR
 * update, calling this function saves off the data needed to find that node
 *
 * Args:
 *    doc : The document that the node lives in
 * nodeid : The id attached to the desired node
 *   next : If true, we'll try to focus on the node following the one above,
 *          otherwise, we'll try to focus on the requested node.
 */
function _setRequestedFocusNode(doc, nodeid, next, win)
{
  // degenerate case - default to something that won't cause an error
  if (!win)
    win = window;

  // we only allow one outstanding focus request
  win._TrFocusRequestDoc = doc;
  win._TrFocusRequestID = nodeid;
  win._TrFocusRequestNext = (next == true);
}


/* If a request was made to focus on a particular node, this function will
 * attempt to get that node.
 */
function _getRequestedFocusNode(win)
{
  // degenerate case - default to something that won't cause an error
  if (win == (void 0))
    win = window;

  if ((win._TrFocusRequestDoc != null)
      && (win._TrFocusRequestID != null))
  {
    var element = _getElementById(win._TrFocusRequestDoc,
                                  win._TrFocusRequestID);
    if (!element)
      return null;

    if (win._TrFocusRequestNext)
    {
      // If "next" was set, the caller doesn't want this node, but the next
      // one. Try to find something that'll accept focus.
      for (var next = element.nextSibling;
           next != null;
           next = next.nextSibling)
      {
        if (_isFocusable(next)
            // we actually DO want to "tab" to links
            || ((_agent.isIE) && (next.nodeName.toLowerCase() == 'a')))
        {
          element = next;
          break;
        }
      }
    }
    return element;
  }
  return null;
}



// Returns the first focusable node under the specified node
function _getFirstFocusable(node)
{
  if ((node == null) || _isFocusable(node))
    return node;

  if (node.hasChildNodes)
  {
    var children = node.childNodes;
    for (var i = 0; i < children.length; i++)
    {
      var child = children[i];
      var firstFocusable = _getFirstFocusable(child);
      if (firstFocusable != null)
        return firstFocusable;
    }
  }

  return null;
}

// Restores the focus to the specified node
function _restoreFocus(node, isFirstFocusable, doc)
{
  if (node == null)
    return;

  // If we are in a scrolled DIV, restoring the focus to the
  // first focusable node may cause the DIV to scroll back to 0,0.
  // So, for now we just avoid restoring the focus in this situation.
  // In the future we should see less cases where scrolling occurs,
  // since we should do a better job locating the correct node to
  // receive the focus.
  var divNode = _getAncestorByName(node, "DIV");
  if (!divNode)
  {
    _pprFocus(node, doc);
  }
  else
  {
    var scrollTop = divNode.scrollTop;
    var scrollLeft = divNode.scrollLeft;

    // If we aren't scrolled at all, or if we are restoring the
    // focus to the correct focusable owner (and not just the
    // first focusable node), then restore the focus.  Otherwise,
    // we do nothing, in order to avoid unnecessary scrolling.
    if (((scrollTop == 0) && (scrollLeft == 0)) || !isFirstFocusable)
    {
      _pprFocus(node, doc);
    }
  }

  // Bug #2753958: IE doesn't seem to want to re-set the focus when we're
  // done with a PPR update if the input element happens to be enclosed
  // within a table. However, if we make a second request, the focus is set
  // correctly. This is limited to the one interesting case.
  if ((_agent.isIE)
      && (node.tagName == 'INPUT')
      && (_getAncestorByName(node, 'TABLE')))
  {
    _pprFocus(node, doc);
  }
}

// Returns an ancestor with the specified name
function _getAncestorByName(
  node,
  ancestorName
  )
{
  ancestorName = ancestorName.toUpperCase();

  while (node)
  {
    if (ancestorName == node.nodeName)
      return node;

    node = node.parentNode;
  }

  return null;
}

// Tests whether one node is a descendent of another
function _isDescendent(
  node,
  ancestorNode
  )
{
  if (node == null)
    return false;

  while (node.parentNode)
  {
    if (node == ancestorNode)
      return true;

    node = node.parentNode;
  }

  return false;
}

// Tests whether the specified node is focusable
function _isFocusable(node)
{
  if (node == null)
    return false;

  var name = node.nodeName.toLowerCase();

  // Links that have a destination are generally focusable
  if (('a' == name) && (node.href))
  {
    // We need to be careful on IE - it seems that
    // IE has problems setting the focus to links
    // which contain a single image.  We see this when
    // IE tries to set the focus to the link around the
    // previous icon in the table.  Actually, this does
    // not seem to be a problem if the link has its
    // id set, so we first check for that.

    // If we're not on IE, or if the link has an id,
    // the link should be focusable
    if (!_agent.isIE || (node.id))
      return true;

    // If we're on IE, we only consider the link to be
    // focusable if it has something other than a single
    // image for its contents.
    var children = node.childNodes;
    if ((children) && (children.length == 1))
    {
      var childName = children[0].nodeName;
      if ('img' == childName.toLowerCase())
        return false;
    }

    return true;
  }

  // Blow off any disabled elements
  if (node.disabled)
    return false;

  // Input elements are also usually focusable
  if ('input' == name)
  {
    // But don't set the focus to hidden fields
    return (node.type != 'hidden');
  }

  // Catch everything else here...
  return (('select' == name) ||
          ('button' == name) ||
          ('textarea' == name));
}

// Evaluates the specified code in the target window
function _eval(targetWindow, code)
{
  if (code == null)
    return;

  // For IE, we use window.execScript().  For Mozilla, we use
  // window.eval().  It would be nice if we could use eval() on
  // IE too, but IE's implementation of eval() always executes
  // the script in the current context, even if some other
  // window is specified.
  if (_agent.isIEGroup)
  {
    targetWindow.execScript(code);
  }
  else
    targetWindow.eval(code);
}

/**
 * Called to identify the input field from an event
 * This is called not only below, but also from LovInput.js.
 */
function _getInputField(event)
{
  var input = (void 0);
  var src = (void 0);

  if (window.event)
  {
    kc = window.event.keyCode;
    src = window.event.srcElement;
  }
  else if (event)
  {
    kc = event.which;
    src = event.target;
  }

  if (src != (void 0)
      && (src.tagName == "INPUT" ||
          src.tagName == "TEXTAREA" ))
    input = src;

  return input;
}

/**
 * Called when a field receives focus.
 * Prepares for a later reset of this field by saving its current value.
 */
function _enterField(
  event
  )
{
  var input;
  var src;
  var retv = true;

  var input = _getInputField(event);

  if (input != (void 0))
  {
    input.form._mayResetByInput = false;
    // save the last valid value for later restoration
    input._validValue = input.value;
    retv = false;
  }

  return retv;
}

/**
 * Resets the form input to its last valid value.
 * This function is called from the onKeyDown for a form input.
 * If called twice in succession for the same form, with the
 * escape keycode both times, this function will reset the form.
 */
function _resetOnEscape(event)
{
  var kc;
  var input = _getInputField(event);

  if (input != (void 0))
  {
    var form = input.form;

    if (kc == 27)  // escape keycode
    {
      // reset the form input to its last valid value
      // providing there is no selection (consistent with IE)

      var hasSelection = false;

      if ((input.selectionStart != (void 0)) &&
          (input.selectionEnd   != (void 0)))
      {
        hasSelection = (input.selectionStart != input.selectionEnd);
      }
      else if (document.selection)
      {
        hasSelection = (document.selection.createRange().text.length != 0);
      }

      if (!hasSelection)
      {
        // always reset the field
        input.value = input._validValue;

        // determine if a full form reset is required
        if (form._mayResetByInput == true)
        {
          // reset the form
          // unset the flag for form reset
          form.reset();
          form._mayResetByInput = false;
        }
        else
        {
          // set the flag for form reset
          form._mayResetByInput = true;
        }
      }

      // consume this event to prevent any browser behavior from kicking in
      return false;
    }
    else // any keycode other than escape
    {
      // unset the flag for form reset
      // since some other key was pressed
      form._mayResetByInput = false;
    }
  }
  return true;
}

/**PH:  Currently, if a browser supports PPR, the _checkLoad function
 * is set as the body onload to perform some initialization, both PPR related
 * and not (such as setting the initial focus).
 * Because this function was not called for non-PPR browsers (like BlackBerry
 * 4.0), the non-PPR initialization was not happening on those browsers.
 * Therefore, I created another function called _checkLoadNoPPR to handle
 * non-PPR related initialization, such as setting the initialFocus, and
 * set the body onload to this method for browsers that do not support PPR.
 */
function _checkLoadNoPPR()
{
  if(_initialFocusID != null)
    _setFocus(_getElementById(document,_initialFocusID));
  _agent.pprUnsupported = true;
}

/**
 * Called by the load handler of each document body to prepare event handlers
 * for forms, etc.
 */
function _checkLoad()
{
  // set focus to the window if a dialog. This fixes the bug where our dialog
  // windows don't have focus, so the first keystroke is ignored. 3544304
  // if I used window.focus(), I caused this bug 3876472 -
  // PAGES COME TO THE FOREGROUND WHEN THE PAGE LOADS
  // We are using _pprdivElementName cuz we need an empty div to set focus
  // to. If we get rid of this element, we'll need to set focus to
  // another element that we know is always on the page.

  // This was causing focus to go off and NEVER COME BACK in the shopping cart
  // demo. I think we can limit this to just a dialog if we can detect that
  // we're in one. For now, we'll have to live with the extra keystroke.
  /*
  if (_agent.isIE)
  {
    var divElement = _getElementById(document, _pprdivElementName);
    if (divElement && divElement.focus)
      divElement.focus();
  }
  */

  // IE has a document.activeElement property. Most other
  // browsers do not (though Firefox 3.0 will).
  if (!_agent.isIEGroup && document.addEventListener)
  {
    document.addEventListener("keyup", _trTrackActiveElement, false);
    document.addEventListener("mousedown", _trTrackActiveElement, false);
  }

  if (document.forms)
  {
    for (var i = 0; i < document.forms.length; i++)
    {
      var form = document.forms[i];

      // Note: event listener functions must already be defined above
      //       no forward references
      if (form.addEventListener) // DOM events
      {
        form.addEventListener('focus', _enterField, true);
        form.addEventListener('keydown', _resetOnEscape, true);
      }
      else if (form.attachEvent) // IE5 events
      {
        form.attachEvent('onfocusin', _enterField);
        form.attachEvent('onkeydown', _resetOnEscape);
      }
    }
  }

  // If we're inside a frameset, and the top frame wants
  // reloads blocked, install a _noReload handler.
  var topWindow = _getTop(self);

  if ((self != topWindow) && topWindow["_blockReload"])
  {
    document.onkeydown = _noReload;
  }

  // Set initialFocus if necessary
  if ((!_agent.isNav) && (_initialFocusID != null))
  {
    var myElement = _getElementById(document,_initialFocusID);

    //PH: Set Focus on element for all browsers.
    if(myElement)
      _setFocus(myElement);
  }

  // Initialize ourselves if we're in a PopupDialog except for Nokia
  if (!_agent.isNokiaPhone)
  {
    TrPopupDialog._initDialogPage();
  }
}


function _getActiveElement()
{
  if (document.activeElement)
    return document.activeElement;
  return window._trActiveElement;
}

function _trTrackActiveElement(e)
{
  window._trActiveElement = e.target;
}

//
// Event handle that blocks keys that lead to a page reloading.
//
function _noReload(e)
{
  if (!e) e=window.event;
  var kc=e.keyCode;
  // F5 and Ctrl-R
  if ((kc==116)||(kc==82 && e.ctrlKey))
  {
    if (e.preventDefault) e.preventDefault();
    e.keyCode=0;
    return false;
  }
}


//
// Deliver a client event with the specified type, source and parameters
// to the handler body.
//
function _handleClientEvent(type, source, params, handlerBody)
{
  var event = new Object();
  event.type = type;
  event.source = source;
  event.params = params;
  var func = new Function("event", handlerBody);
  return func(event);
}


//
// APIs dealing with cookies.  We currently have no supported
// public functions.  _getCookie() and _setCookie() are good candidates.
//

function _getCookie(name)
{
  var dc = document.cookie;

  var value = "";
  var prefix = name + "=";
  if (dc)
  {
    // Look for the cookie name in in the middle.
    var begin = dc.indexOf("; " + prefix);

    if (begin < 0)
    {
      // Not there: look for it at the beginning
      begin = dc.indexOf(prefix);
      if (begin > 0)
        begin = -1;
    }
    else
      // Found it - now skip over the colon and space
      begin += 2;

    if (begin >= 0)
    {
      var end = dc.indexOf(";", begin);
      if (end < 0)
        end = dc.length;

      value = unescape(dc.substring(begin + name.length + 1, end));
    }
  }

  return value;
}

//
// Sets a cookie value.
// This function isn't especially general (yet) as it doesn't
// allow overriding the domain, path, or expiry.
//
function _setCookie(name, value)
{
  // Compute the domain to scope as widely as is legit
  // by scoping off.
  // =-=AEW "localhost" just doesn't work.  I don't know how to
  // deal with this...  It'll confuse developers, even though
  // it will never have any real impact.
  var domain = window.location.host;

  /* =-=AEW The current Oracle Cookie design guidelines require
     cookies to _not_ be scoped widely.  So don't!
  var periodIndex = domain.indexOf(".");
  if ((periodIndex >= 0) &&
      (domain.lastIndexOf(".") != periodIndex))
  {
    // But don't scope off anything that's entirely a number,
    // since then we're probably dealing with an IP address
    var startOfDomain = domain.substr(0, periodIndex);
    if (!(((startOfDomain * startOfDomain) == 0) ||
          ((startOfDomain / startOfDomain) == 1)))
      domain = domain.substr(periodIndex);
  }
  */

  var colonIndex = domain.indexOf(":");
  if (colonIndex >= 0)
    domain = domain.substr(0, colonIndex);

  // Expire 10 years after today
  var expires = new Date();
  expires.setFullYear(expires.getFullYear() + 10);

  // And here's the cookie:
  // (Reordering the parameters seemed to break some browsers)
  var curCookie = name + "=" + value +
      "; path=/;domain=" + domain + "; expires=" + expires.toGMTString();

  document.cookie = curCookie;
}


//
// Compute the time zone ID, of form GMT+-XX:YY
//
function _getTimeZoneID()
{
  // Get the time zone offset, then flip the sign, as this
  // is opposite in meaning to the time zone ID
  var tzOffset = -(new Date()).getTimezoneOffset();
  var newTZ;

  // Build up the name of the time zone
  if (tzOffset > 0)
    newTZ = "GMT+";
  else
  {
    newTZ = "GMT-";
    tzOffset = -tzOffset;
  }

  var minutes = "" + tzOffset % 60;
  if (minutes.length == 1)
    minutes = "0" + minutes;
  return (newTZ + (Math.floor(tzOffset / 60)) + ":" + minutes);
}


//
// Returns true if the current document reads left to right.
//
function _isLTR()
{
  return document.documentElement["dir"].toUpperCase() == "LTR";
}


//
// _isSubmittingElement : Is the given element likely to submit?
//
function _isSubmittingElement(element)
{
  var isSub = false;
  var eltype = element.nodeName.toUpperCase();
  // Assume any button click is wanted
  if (eltype == "BUTTON")
  {
    isSub = true;
  }
  else if (eltype == "IMG")
  {
    // If the click was on an image, check to see if the image
    // is inside a link element.
    var pnode = element.parentNode;
    var ptype = pnode.nodeName.toUpperCase();
    if (('A' == ptype) && (pnode.href))
    {
      // OK, it's a link, now check if the onClick goes to one of our
      // submit functions.
      var oc = "" + pnode["onclick"];
      if ((oc != (void 0)) && (oc != null))
      {
        isSub = ((oc.indexOf("submitForm") > 0)
                 || (oc.indexOf("_uixspu") > 0)
                 || (oc.indexOf("_adfspu") > 0)
                 || (oc.indexOf("_addRowSubmit") > 0));
      }
    }
  }
  return isSub;
}



// Get the keycode from an event
function _getKC(event)
{
  if (window.event)
    return window.event.keyCode;
  else if (event)
    return event.which;
  return -1;
}


// Returns true if a form has been submitted a "short" time before newDate
function _recentSubmit(newDate)
{
  if (_lastDateSubmitted)
  {
    var diff = newDate - _lastDateSubmitted;
    if ((diff >= 0) && (diff < 200))
      return true;
  }
  return false;
}

// Returns true if a form has been reset a "short" time before newDate
function _recentReset(newDate)
{
  if (_lastDateReset)
  {
    var diff = newDate - _lastDateReset;
    if ((diff >= 0) && (diff < 200))
      return true;
  }
  return false;
}

function _radioSet_uixspu(f,v,e,s,pt,p,o)
{
  _radioSet_adfspu(f,v,e,s,o);
}

function _radioSet_adfspu(f,v,e,s,o)
{
  if (window._pprBlocking)
    return;

  // Once again we've got timing issues. When the user clicks on the
  // text of a radio button, we get an onClick for the enclosing span before we
  // get the onClick for the button itself. This wouldn't be so bad if the
  // selected state was already changed, but it isn't. So we have to send the
  // second click if there is one, otherwise we have to send the first (only)
  // click. There's already code in submitForm to check for multiple submits
  // within 200ms, so we just send the first click after waiting 200ms.
  //
  // Of course the obvious answer to this is to use onChange instead of
  // onClick, but IE doesn't deliver onChange events to the container

  if (_pendingRadioButton)
  {
    // This is the second onClick call. We want to run the call to adfspu.
    // Eventually submitform will be called, do a submit, and set the
    // _lastDateSubmitted.

    // clear the pending flag for next time
    _pendingRadioButton = false;
    // and call
    _adfspu(f,v,e,s,o);
  }
  else
  {
    // This is the first click.

    // Remember that we've got a pending click.
    _pendingRadioButton = true;

    // Now build up a string representation of the call,
    // and put it into a timeout.

    // clear the pending flag for next time - in case there was no second click.
    var spucall = "_pendingRadioButton=false;_adfspu(";
    // Form
    if ((f != (void 0)) && (f != null))
      spucall += "'" + f + "'";
    spucall += ",";
    // Validation
    if (v != (void 0))
      spucall += v;
    spucall += ",";
    // Event
    if ((e != (void 0)) && (e != null))
      spucall += "'" + e + "'";
    spucall += ",";
    // Source
    if ((s != (void 0)) && (s != null))
      spucall += "'" + s + "'";

    // RadioSet does not pass an object
    spucall += ");";
    window.setTimeout(spucall, 200);
  }
}

/** This function is called from _spinboxRepeat.
 * This function increments or decrements the value that is in the
 * input field by the stepSize. If the max/min is reached, check circular.
 * If circular is true, then circle the number around.
 * we default circular for now, because we do not support it yet.
 * Else, stop at the max or min.
 */
function _stepSpinboxValue(id, increment, stepSize, min, max)
{
   var circular = false;
   var input = _getElementById(document, id);
   if (input)
   {
      var value = input.value;
      if (isNaN(value) || isNaN(stepSize) || isNaN(min) || isNaN(max))
      {
        alert("value, stepSize, min, and max must all be numbers. value: "+
               value+", stepSize: "+stepSize+", min: "+min+", max: "+max);
        return false;
      }
      if (increment)
      {
        var incrementedValue = parseFloat(value) + parseFloat(stepSize);
        if (incrementedValue < max)
              input.value = incrementedValue;
        else if (circular)
              input.value = min;
        else input.value = max;
      }
      else
      {
        var decrementedValue = parseFloat(value) - parseFloat(stepSize);

        if (decrementedValue > min)
          input.value = decrementedValue;
        else if (circular)
          input.value = max;
        else input.value = min;
      }
      return true;
   }
   return false;
}

/* This function is called when the inputNumberSpinbox component's spinbox
 * buttons are released (onmouseup).
 * This function stops the spinboxTimer.
 * The spinboxTimer calls _stepSpinboxValue in one second increments.
 */
function _clearSpinbox()
{
  window.clearTimeout(_spinboxRepeat.timer);
  _spinboxRepeat.functionString = null;
}

/**
  * This function is called when the inputNumberSpinbox component's
  * spinbox buttons are pressed. This is called onmousedown.
  * It calls the _stepSpinboxValue function to increment or decrement
  * the input element's value. We call this repeatedly every second.
  * onmouseup the component calls _clearSpinbox which clears the timeout.
  */
function _spinboxRepeat(id, increment, stepSize, min, max)
{
  // increment/decrement
  var success = _stepSpinboxValue(id, increment, stepSize, min, max);
  // if not successful, then clear the timeout and return
  if (!success)
  {
    window.clearTimeout(_spinboxRepeat.timer);
  }
  else
  {
    if (_spinboxRepeat.functionString == null)
    {
      // setup the function to pass to the timeout.
      _spinboxRepeat.functionString =
          "_spinboxRepeat('"+id+"',"+increment+
          ","+stepSize+","+min+","+max+");";
    }
    _spinboxRepeat.timer =
      window.setTimeout(_spinboxRepeat.functionString, 1000);
  }

}

//PH:This method returns the 'event' object
function _getEventObj()
{
  if(typeof(event) == 'undefined')
    return window.event;
  else
    return event;

  return null;
}

//***********************************************************
// "Object Oriented" js below
//***********************************************************
/**
 * User interfaces utility methods
 */
var TrUIUtils = new Object();

/**
 * Remove leading and trailing whitespace
 */
TrUIUtils.trim = function(
data)
{
  if (data != null && (typeof data) == 'string')
    return data.replace(TrUIUtils._TRIM_ALL_RE, '');

  return data;
}

// regular expression to gather whitespace at beginning and end of line
TrUIUtils._TRIM_ALL_RE = /^\s*|\s*$/g;


/**
 * Creates a function instance that will callback the passed in function
 * with "thisObj" as "this".  This is extremely useful for creating callbacks
 */
TrUIUtils.createCallback = function(thisObj, func)
{
  // create a function that sets up "this" and delegates all of the parameters
  // to the passed in function
  var proxyFunction = new Function(
    "var f=arguments.callee; return f._func.apply(f._owner, arguments);");

  // attach ourselves as "this" to the created function
  proxyFunction._owner = thisObj;

  // attach function to delegate to
  proxyFunction._func = func;

  return proxyFunction;
}

/**
 * Get the client window size.
 * TODO - make this public?
 *
 * @return {Object} the client size of the window. The returned object will have w and h properties.
 */
TrUIUtils._getWindowClientSize = function()
{
  var func;

  if (TrUIUtils['_getWinClientSize'] == null)
  {
    // IE is abnormal
    if(_agent.isIE)
    {
      TrUIUtils._getWinClientSize = function()
      {
        var e = ((document.compatMode == "BackCompat") ? document.body : document.documentElement);
        return { w: e.clientWidth, h: e.clientHeight };
      }
    }
    else
    {
      TrUIUtils._getWinClientSize = function()
      {
        return { w: window.innerWidth, h: window.innerHeight };
      }
    }
  }

  return TrUIUtils._getWinClientSize();
}

/**
 * Return the offset bounds of an element
 * TODO - make this public?
 *
 * @param elem {String or Element} the ID of an element or an element reference
 * @return {Object} the returned object will have x, y, w and h properties.
 * Returns null if the element does not exist
 */
TrUIUtils._getElementBounds = function(elem)
{
  if (typeof(elem) == "string")
  {
    elem = document.getElementById(elem);
  }
  if (!elem)
  {
    return null;
  }
  var loc = TrUIUtils._getElementLocation(elem);
  return { x: loc.x, y: loc.y, w: elem.offsetWidth, h: elem.offsetHeight };
}

/**
 * Get the location of an element in relation to the view port.
 * This will return the same co-ordinates as browser events (i.e. mouse event locations).
 * TODO - make this public?
 *
 * @param elem {String or Element} the ID of an element or an element reference
 * @return {Object} the location on the page. The returned object will have x and y properties.
 * Returns null if the element does not exist
 */
TrUIUtils._getElementLocation = function(elem)
{
  if (typeof(elem) == "string")
  {
    elem = document.getElementById(elem);
  }
  if (!elem)
  {
    return null;
  }

  var func;

  if (TrUIUtils['_getElemLoc'] == null)
  {
    // if possible, use more accurate browser specific methods
    if (_agent.isGecko)
    {
      TrUIUtils._getElemLoc = function(elem)
      {
        var doc = elem.ownerDocument;
        var box = doc.getBoxObjectFor(elem);
        var loc = { x: box.screenX, y: box.screenY };
        box = doc.getBoxObjectFor(doc.documentElement);
        loc.x -= box.screenX;
        loc.y -= box.screenY;
        return loc;
      }
    }
    else if(_agent.isIE)
    {
      TrUIUtils._getElemLoc = function(elem)
      {
        var doc = elem.ownerDocument;
        var rect = elem.getBoundingClientRect();
        var loc = { x: rect.left, y: rect.top };
        var docElem = doc.documentElement;
        var scrollLeft = docElem.scrollLeft;

        var rtl = docElem["dir"] == "rtl";
        // IE scroll bar adjustment
        if (rtl)
        {
          scrollLeft += docElem.clientWidth - docElem.scrollWidth;
        }
        loc.x -= docElem.clientLeft - scrollLeft;
        loc.y -= (docElem.clientTop - docElem.scrollTop);
        return loc;
      }
    }
    else
    {
      TrUIUtils._getElemLoc = function(elem)
      {
        var win = elem.ownerDocument.contentWindow;
        // use offset* properties to determine location
        var curleft = 0;
        var curtop = 0;
        for (var obj = elem; obj && obj != win; obj = obj.offsetParent)
        {
          curleft += obj.offsetLeft;
          curtop += obj.offsetTop;
        }
        return { x: curleft, y: curtop };
      }
    }
  }

  return TrUIUtils._getElemLoc(elem);
}


/**
 * Get a css property as its JavaScript variable name
 */
TrUIUtils._cssToJs = function(prop)
{
  var jsProp = '';
  var upperNext = false;
  for (var c = 0; c < prop.length; c++)
  {
    if (prop.charAt(c) == '-')
    {
      upperNext = true;
      continue;
    }

    if (upperNext)
    {
      jsProp += prop.charAt(c).toUpperCase();
    }
    else
    {
      jsProp += prop.charAt(c);
    }

    upperNext = false;
  }

  return jsProp;
}

/**
 * Get a calculated CSS style value
 */
TrUIUtils._getStyle = function(element, prop)
{
  if (element.currentStyle)
  {
    // remove dashes and uppercase next letter
    var jsProp = this._cssToJs(prop);
    return element.currentStyle[jsProp];
  }
  else if (window.getComputedStyle)
  {
    return document.defaultView.getComputedStyle(element, '')
      .getPropertyValue(prop);
  }
  return '';
}

/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */

/**
 * Note that the only function in this file that is supported is the openWindow
 * function.
 * All other functions may change without notice.
 */

var ADFDialogReturn = new Array();

function _launchDialog(
  srcURL,
  windowName,
  features,
  formName,
  postbackId,
  partial)
{
  // Make sure we're calling submitForm() on the correct document
  var myself = self;
  var returnFromDialog;

  if (partial)
  {
    returnFromDialog = function()
    {
      myself._submitPartialChange(formName, 0, {rtrn:postbackId});
    };
  }
  else
  {
    returnFromDialog = function()
    {
      myself.submitForm(formName, 0, {rtrn:postbackId});
    };
  }

  var index = ADFDialogReturn.length;
  ADFDialogReturn[index] = returnFromDialog;
  srcURL = srcURL + "&_rtrnId=" + index;
  openWindow(window, srcURL, windowName, features, 1);
}


/**
 * Opens a new window, makes it active, and returns the Window
 * object.
 * <p>
 * @param parentWindow The window opening this window.  For modal
 *                     windows, this is the window that the
 *                     new window will be modal to. REQUIRED.
 * @param srcURL       URL for the contents of the new window.
 *                     REQUIRED.
 * @param windowName   Name that identifies the new window for
 *                     URLS. Required if a closeCallback is to be used.
 * @param features     Javascript object containing name/value pairs
 *                     of features to apply to the new window,
 * @param isModal      True if the window should be modal.
 * @param kind         Either 'document' or 'dialog'.  The default is
 *                     dependent on the value of isModal--'document'
 *                     for modeless, 'dialog' for modal.
 * @param closeCallback  Callback method called when the window is closed.
 *                      Note: Under certain adverse conditions (for example,
 *                      if the parentWindow navigates to a new page), it is
 *                      possible that the closeCallback may not be called.
 */
function openWindow(
  parentWindow,
  srcURL,
  windowName,
  features,
  isModal,
  kind,
  closeCallback
  )
{
  if (parentWindow)
  {
    // default modality if none specified
    if (isModal == (void 0))
      isModal = false;

    // if the kind isn't specified, default the kind off of the modality
    if (!kind)
    {
      kind = (isModal) ? "dialog" : "document";
    }

    // default the window name to "_blank" if no
    // name is specified, in order to guarantee that a new window is
    // opened
    if (!windowName)
      windowName = "_blank";

    //
    // pick the correct defaults and overrides
    //
    var defaults = _featureDefaults[kind];

    if (defaults == (void 0))
    {
      kind = "document";
      defaults = _featureDefaults[kind];
    }

    var overrides = (isModal)
                      ? _modalFeatureOverrides
                      : _modelessFeatureOverrides;

    // determine the avialable features
    var agentFeatures = (_agent.isIE)
                          ? _ieFeatures
                          : _nnFeatures;

    // we'll be hammering the features object, so make a copy
    var featuresCopy = null;
    if (features)
    {
      featuresCopy = new Object();
      for (var f in features)
      {
        featuresCopy[f] = features[f];
      }
    }

    //
    // build up the feature string
    //
    var featureString = "";

    // loop through the available features for this platform
    for (var featureName in agentFeatures)
    {
      // get the overridden value of the feature
      var featureValue = overrides[featureName];

      if (featureValue == (void 0))
      {
        // get the value of the feature if it isn't overridden
        if (featuresCopy)
        {
          featureValue = featuresCopy[featureName];
          delete featuresCopy[featureName];
        }

        // if no value, get the default value, if any
        if (featureValue == (void 0))
          featureValue = defaults[featureName];
      }

      if (featureValue != (void 0))
      {
        // check if this is a boolean value
        var isBoolean = _booleanFeatures[featureName] != (void 0);

        // output the value
        if (featureValue || !isBoolean)
        {
          // add the feature name
          featureString += featureName;

          // add the value for nonboolean features
          if (!isBoolean)
          {
            featureString += "=" + featureValue;
          }

          // add separator between this and the next
          featureString += ",";
        }
      }
    }

    // Now tack on all the extra features that the user has requested.
    // These may or may not have meaning to the browser's implementation of
    // window.open().
    for (var f in featuresCopy)
    {
      featureString += f;
      if (featuresCopy[f])
        featureString += "=" + featuresCopy[f];

      featureString += ",";
    }

    // trim off last separator
    if (featureString.length != 0)
    {
      featureString = featureString.substring(0, featureString.length - 1);
    }

    // register the closing callback
    if (closeCallback)
    {
      _setDependent(parentWindow, windowName, closeCallback);
    }

    // open an empty window
    var newWindow = parentWindow.open(srcURL, windowName, featureString);

    // Check for popup blockers.
    // This will certainly all change, but now (early 2005) the popup blockers
    // work thusly:
    //
    // Google:  The return value from window.open is null.
    // Safari:  The return value from window.open is null.
    // FireFox: The return value from window.open is a valid object
    //          with no properties.
    // Yahoo!:  The return value from window.open is a valid object which
    //          throws an exception when you try to do anything with it.
    var usableWindow = false;
    if (newWindow != null)
    {
      var propCount = 0;
      try
      {
        for (p in newWindow)
        {
          propCount++;
          break;
        }
        // if (propCount == 0) this is Firefox popup blocker
        if (propCount > 0)
          usableWindow = true;
      }
      catch (e)
      {
        // Yahoo! toolbar throws an exception when you try to access any of the
        // new window's properties.
      }
    }
    // else this is the Safari or Google Toolbar popup blocker

    if (!usableWindow)
    {
      _setDependent(parentWindow, windowName, (void 0));
      if (_AdfWindowOpenError != null)
        alert(_AdfWindowOpenError);
      return;
    }

    // detect the bogus ie4 and turn off modal windows
    var atMostIE4 = _agent.atMost("ie", 4.99);
    var alphaFilter = false;

    // document of the parent window
    var parentDoc = parentWindow.document;

    // body of the parent window
    var parentBody = parentDoc.body;

    if (isModal && !atMostIE4)
    {
      if (_agent.atLeast("ie", 4))
      {
        var dimmer = parentDoc.getElementById("_trDialogDimmer");
        if (dimmer == null)
        {
          // Display a div over the browser viewport that will give the entire page the appearance
          // of being disabled:
          dimmer = parentDoc.createElement("div");
          dimmer.id = "_trDialogDimmer";
          var dimmerStyle = dimmer.style;
          dimmerStyle.position = "absolute";
          dimmerStyle.zIndex = "32000";
          dimmerStyle.backgroundColor = "#FFFFFF";
          dimmerStyle.filter = "alpha(opacity=50)";

          // Position the dimmer element, account for scrolling:
          var docElement = parentDoc.documentElement;
          var width = Math.max(docElement.offsetWidth, docElement.scrollWidth);
          var height = Math.max(docElement.offsetHeight, docElement.scrollHeight);
          dimmerStyle.width = width + "px";
          dimmerStyle.height = height + "px";
          dimmerStyle.top = "0px";
          dimmerStyle.left = "0px";

          // Add the dimmer element to the body:
          parentBody.appendChild(dimmer);

          alphaFilter = true;
        }
      }

      // Capture mouse events.  Note: we special-case IE/Windows,
      // and only apply the capture after window.open() has been
      // called.  See below for details.
      // XXXSafari: What to do for Safari?
      if (_agent.isGecko)
      {
        // this should work, but doesn't appear to
        if (parentBody != (void 0))
          _addModalCaptureGecko(parentBody);
      }

      parentWindow.onfocus = _onModalFocus;
    }

    // Apply mouse capture for IE/Windows.  Starting in IE 6.0.2800,
    // if we apply the capture before calling window.open(), the capture
    // is lost immediately when the secondary window receives the
    // focus.  To make matters worse, the _onModalFocus handler that
    // we register on the parent window is no longer invoked (not
    // sure why?!).  The end result is that the user can interact
    // with the parent window even when a modal child is displayed.
    // Delaying our setCapture() call until after window.open()
    // seems to work around the problem.
    if (isModal && (_agent.atLeast("ie", 5) && _agent.isWindows))
    {
      _addModalCaptureIE(parentBody);

      // Set up an onlosecapture handler so that we can
      // restore the capture if we unexpectedly lose it.
      // this code has been removed as it caused IE to "lock up" when
      // IE7 is set to force new windows to open in tabs instead of new
      // windows.
      //parentBody.onlosecapture = _onModalLoseCapture;

      // Popup blockers!
      // BUG 4428033 - ECM: MENUS BECOM DISABLED AFTER RAISING A DIALOG
      //                    FROM A COMMANDMENUITEM
      // When a popup blocker is installed, the onunload event is often
      // not delivered to listeners attached in the scope of the opened
      // modal window.  However, onunload events _are_ delivered to
      // listeners attached in the opener context.
      //
      // However, this onunload event listing is only permitted within the
      // same scripting domain. We should really verify if the domains match
      // or not, but relative URLs are always in the same domain, so just
      // test for absolute URLs instead.
      //
      // A quick check for absolute URL is to test the presence of an
      // unescaped colon.
      //
      var isAbsolute = (srcURL != null && srcURL.indexOf(':') != -1);
      if (!isAbsolute)
      {
        var removeCapture = new Function("e", "_removeModalCaptureIE(window.document.body)");
        newWindow.attachEvent("onunload", removeCapture);
      }
    }

    /*
    // create the content for the window.  We use a frameset so that
    // the content can change without firing the window close event.
    var realSrcURL = "<html>";

    //realSrcURL += "<head><scr";
    //realSrcURL += 'ipt src="/images/jsLibs/Window.js"></head>';

    realSrcURL += '<frameset rows="100%,*" border="0" onunload="_checkUnload(event)"><frame src="' +
          srcURL +
          '"></frameset></html>';

    newWindow.document.write(realSrcURL);
    newWindow.document.close();
        */

    if (isModal && !atMostIE4)
    {
      _setDependent(parentWindow, "modalWindow", newWindow);
    }

    // If there are any poll commands registered, they should be deactivated when the
    //  modal window gets launched. They need to be reactivated upon closing the
    //  modal dependent using _pollWhenModalDependentCloses().
    // Cleaner alternative to _pollWhenModalDependentCloses() could have been to use a
    //  registered callback like _checkUnload(), _onModalFocus(), _onModalLoseCapture().
    //  But none of these were reliable for either of...
    //   1. One of them is to workaround IE grabbing focus on parent
    //       while dialog launch in progress.
    //   2. Does not get called when dialog is dismissed using the close
    //       button on the window, particularly for case of dialogs
    //       launched using openWindow().
    if (isModal && self._pollManager)
    {
      _pollManager.deactivateAll();
      _pollWhenModalDependentCloses();
    }

    // make the active window
    newWindow.focus();


    // Set up a timer to make sure that we reset the alpha filter.
    if (alphaFilter)
    {
      parentWindow.setTimeout("_clearBodyModalEffects('alpha')", 1000);
    }

    return newWindow;
  }
  else
  {
    return null;
  }
}

// Keeps checking for absence of a modal dependent, when found
//  reactivates all poll, and stops checking any further
function _pollWhenModalDependentCloses()
{
  if (!_getValidModalDependent(self))
  {
    _pollManager.reactivateAll();
  }
  else
  {
    // pu: Call thyself to check again after a second
    //  If more accuracy required, set it to a millisecond
    self.setTimeout("_pollWhenModalDependentCloses()", 1000);
  }
}

/**
 * onFocus override when modal windows are up.
 * Handles moving the focus to the top and suppressing IE mouse clicks.
 */
function _onModalFocus()
{
  var theBody = self.document.body;

  // =-=ags Note: This used to call _getValidModalDependent(),
  //        but now instead we call _getModalDependent() and
  //        explicitly check the value of the closed property.
  //        The reason for this is that on Gecko, _onModalFocus
  //        is called before _checkUnload().  This means that by
  //        the time _checkUnload() is called, _getValidModalDependent()
  //        will have already removed the modal window from the
  //        dependents list.  As a result, the capture event handlers
  //        added on Gecko are never removed.
  var modalDependent = _getModalDependent(self);

  var hasCapture = _agent.atLeast("ie", 5) && _agent.isWindows;

  if (modalDependent && !modalDependent.closed)
  {
    modalDependent.focus();

    // reset the capture on IE since it is released whenever
    // the focus moves to another window
    if (hasCapture)
    {
      theBody.setCapture();
    }
  }
  else
  {
    if (hasCapture)
    {
      theBody.onlosecapture = null;
      _removeModalCaptureIE(theBody);
    }
  }
}


// A self-rescheduling function which clears out capture or alpha filtering
// when modal windows are closed.  These effects are normally removed in
// the modal window's onunload() handler.  However, the onunload handler
// may not be called if the modal window is closed by the user before
// loading completes.  This function is used in conjunction with
// Window.setTimeout() to make sure that the effects are cleared
// in this case.
function _clearBodyModalEffects(effect)
{
  if (_getValidModalDependent(self) != null)
  {
    // If the modal window is still shown, re-schedule ourselves
    self.setTimeout("_clearBodyModalEffects('" + effect + "')", 1000);
  }
  else
  {
    if (effect == 'alpha')
    {
      // No modal dependent - clear out the alpha filter and don't bother rescheduling.

      // Locate the dialog dimmer element:
      var dimmerDoc = self.document;
      var dimmer = dimmerDoc.getElementById("_trDialogDimmer");
      if (dimmer != null)
      {
        // Remove the dimmer div that covers the browser viewport:
        dimmerDoc.body.removeChild(dimmer);
      }
    }
  }
}

/**
 * Returns the dependent which is modal and is still open.
 */
function _getValidModalDependent(
  parentWindow
  )
{
  var modalDependent = _getModalDependent(parentWindow);

  if (modalDependent)
  {
    if (modalDependent.closed)
    {
      _setDependent(parentWindow, "modalWindow", (void 0));
      modalDependent = (void 0);
    }
  }

  return modalDependent;
}


/**
 * Sizes the window to its preferred size.
 */
function _sizeWin(
  theWindow,
  extraWidth,
  extraHeight,
  params
  )
{
  var isGecko    = _agent.isGecko;
  var isIE       = _agent.isIE;
  var isSafari   = _agent.isSafari;
  var isStandard = (isGecko || isSafari);

  if (!(isStandard || (isIE && _agent.isWindows)))
    return;

  /*
  // =-= bts theoretically, this would be all we need to do for Mozilla,
  //         but the implementation appears to be sub-optimal for our case
  if (isGecko)
  {
    theWindow.sizeToContent();
    return;
  }
  */
  var body =  theWindow.document.body;

  if (body)
  {
    // width of the inside
    var newWidth = (!isIE && (body.scrollWidth > body.clientWidth))
                     ? body.scrollWidth
                     : _getBodyWidth(body, body.offsetWidth, body.offsetLeft);
    var newHeight = 0;

    var hasParams = params && ((params['H'] && params['H'] > 0)  || (params['W'] && params['W'] > 0));
    // if the height was not explicitly set, change to auto forcing
    // recalculation of  the offsetHeight.  FireFox doesn't always detect
    // that a PPR has changed the content size.
    var bodyStyle = body.style;
    if (!hasParams && (!bodyStyle.height || bodyStyle.height.length == 0))
    {
      bodyStyle.height = "auto";
    }

    if (isStandard)
    {
      newHeight = body.offsetHeight + (window.outerHeight - window.innerHeight);

      // random extra space added in to make this work
      newHeight += 30;

      // add in the window frame plus padding
      // =-=AEW For some bizarre reason, I'm seeing cases
      // where the window's outerWidth is much, much smaller than
      // the body's offsetWidth, which results in the Gecko
      // window shrinking.  Block this, though it'd be nice
      // to know what's really going on!
      if (window.outerWidth > body.offsetWidth)
        newWidth  += (window.outerWidth - body.offsetWidth);
    }
    else
    {
      newHeight = body.scrollHeight + (body.offsetHeight - body.clientHeight);
      // if this method supported windows with toolbars, we would need to
      // add another 67 here, rather than the aribitrary 8 pixels
      newHeight += 21;

      // add in the padding plus bogus extra size
      newWidth += body.offsetWidth - body.clientWidth + 16;
      // add in the margins (MS bogusly uses Strings for these)

      if(body.tagName=='BODY')
      {
        newHeight += parseInt(body.topMargin) + parseInt(body.bottomMargin);
        newWidth  += parseInt(body.leftMargin) + parseInt(body.rightMargin);
      }
    }
    //
    // allow the size to be a little bigger than currently necessary.
    // This is useful when we will be paging through multiple items and
    // later pages  might need a slightly larger window than the initial
    // page.
    if (extraWidth)
      newWidth += extraWidth;

    if (extraHeight)
      newHeight += extraHeight;

    // Make sure that width and height are at least as big as minimum requested
    if (params != (void 0))
    {
      if (params['W'])
      {
        var minWidth = 0 + params['W'];
        if (newWidth < minWidth)
          newWidth = minWidth;
      }

      if (params['H'])
      {
        var minHeight = 0 + params['H'];
        if (newHeight < minHeight)
          newHeight = minHeight;
      }
    }

    var newWin = _getTop(theWindow);

    // keep a bottom/right pad of at least 5% of the available screen
    var avLeft = isIE ? 0 : newWin.screen.availLeft;
    var avTop = isIE ? 0 : newWin.screen.availTop;
    var maxSHeight = newWin.screen.availHeight * 0.95;
    var maxSWidth = newWin.screen.availWidth * 0.95;
    // adjust if necessary
    if (newHeight > maxSHeight)
      newHeight = maxSHeight;
    if (newWidth > maxSWidth)
      newWidth = maxSWidth;

    // Finally, we can resize the window.
    // theWindow.parent.resizeTo(newWidth, newHeight);
    try
    {
      newWin.resizeTo(newWidth, newHeight);
    }
    catch (e)
    {
      // ignore errors. An error will be throw if the new window opened in a tab
      // instead of a new browser window as resizing the main window is prohibited by security
      ;
    }

    // Check to make sure that our resize hasn't put the
    // window partially off screen.
    var wLeft = isIE ? newWin.screenLeft: newWin.screenX;
    var wTop = isIE ? newWin.screenTop: newWin.screenY;
    var moveit = false;

    // If we are off screen horizontal or vertical, center in that direction
    if ((wLeft + newWidth) > (avLeft + maxSWidth))
    {
      wLeft = (newWin.screen.availWidth - newWidth)/2;
      moveit = true;
    }

    if ((wTop + newHeight) > (avTop + maxSHeight))
    {
      wTop = (newWin.screen.availHeight - newHeight)/2;
      moveit = true;
    }

    if (moveit)
    {
      newWin.moveTo(wLeft, wTop);
    }
  }
}





/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 * 
 *  http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */


function _tableSort(
 formName,
 vld,
 src,
 val,
 order)
{
  _submitPartialChange(formName,vld,
                       {event:'sort',
                        source:src,
                        value:val,
                        state:order});
  return false;
}


// Constructor
function CollectionComponent(
  formName,
  name
  )
{
  this._formName = formName;
  this._name = name;
}

// returns the name of the form
CollectionComponent.prototype.getFormName = function()
{
    return this._formName;
};

// returns the name of this component
CollectionComponent.prototype.getName = function()
{
    return this._name;
};

// gets a form input element with the given name that is inside this
// component.
CollectionComponent.prototype.getFormElement = function(elementName)
{
    var form = document.forms[this.getFormName()];
    var formElementName = this.getName()+":"+elementName;
    var element = form[formElementName];
    return element;
};

// extends the CollectionComponent class to perform submits.
// @param eventParam the event parameter name. eg:"event"
// @param sourceParam the source parameter name. eg:"source"
CollectionComponent.defineSubmit = function(eventParam,sourceParam)
{
    // check to make sure we are called only once:
    if (this._eventParam != (void 0))
      return;
    CollectionComponent.prototype._eventParam = eventParam;
    CollectionComponent.prototype._sourceParam = sourceParam;
    CollectionComponent.prototype._pTargetsParam = "partialTargets";

    // adds the given name,value pair to the submit parameters
    CollectionComponent.prototype.addParam = function(paramName,paramValue) {
      if (this._params == (void 0))
      {
        this._params = new Object();
      }
      this._params[paramName] = paramValue;
    }

    // submit the form with the given event.
    // "link" is the DOM <a> that triggered this submission.
    CollectionComponent.prototype.submit = function(event, link) {
      this.addParam(this._eventParam, event);
      this.addParam(this._sourceParam, this.getName());
      var params = this._params;
      var pTargets = params[this._pTargetsParam];

      if (link != (void 0))
      {
        var focusId = link.id;
        if (focusId != (void 0))
        {
          // this is the most reliable way to ensure that the
          // focus is reset in both moz and IE:
          _setRequestedFocusNode(document, focusId, false, window);
        }//VAC fix for PDA's
        if (pTargets == (void 0))
        {
          pTargets = this.getName();
          params[this._pTargetsParam] = pTargets;
        }
      }

      // immediate defaults to false, meaning there should be client validation
      var validate = this._validate;
      if ( validate == (void 0))
        validate = 1;

      var subFunc = submitForm;
      if (pTargets != (void 0))
      {
        subFunc = _submitPartialChange;
      }
      subFunc(this.getFormName(), validate, params);
      return false;
    };
};

// enhances the CollectionComponent class to perform multiple selection.
// @param selectedKey the name of the checkbox element.
// @param selectedModeKey the name of the 'selectedMode' hidden form field.
// @param auto whether to autosubmit
CollectionComponent.defineMultiSelect = function(selectedKey,selectedModeKey,auto)
{
    // check to make sure we are called only once:
    if (this._selectedKey != (void 0))
      return;
    // these are on the prototype because they only need to be defined
    // once:
    CollectionComponent.prototype._selectedKey = selectedKey;
    CollectionComponent.prototype._selectedModeKey = selectedModeKey;


    // returns the number of rows displayed on screen for this component
    CollectionComponent.prototype.getLength = function() {
        var boxes = this._getBoxes();
        return boxes.length;
    };

    // preforms a select all or select none depending on the parameter:
    CollectionComponent.prototype.multiSelect = function(selectAll) {
        var boxes = this._getBoxes();
        for(var i=0; i<boxes.length; i++)
        {
            var box = boxes[i];
            box.checked = selectAll;
        }
        var selectedMode = this.getFormElement(this._selectedModeKey);
        if (selectAll)
        {
          selectedMode.value = "all";
        }
        else
        {
          selectedMode.value = "none";
        }
        if (auto) // bug 4612379
        {
          _submitPartialChange(this.getFormName(), 1, null);
        }
    };


    // Gets all the select check boxes (or radio buttons):
    CollectionComponent.prototype._getBoxes = function() {
        var boxes = this.getFormElement(this._selectedKey);
        // if there was only one checkBox/radioButton on the page
        // then the value returned will not be an array so convert it
        // into one:
        if (boxes.length == (void 0))
        {
          var boxes_t = new Array(1);
          boxes_t[0] = boxes;
          boxes = boxes_t;
        }
        return boxes;
    };
};

// enhances this class to perform tree functions.
CollectionComponent.defineTree =
    function(eventParam,
             sourceParam,
             pathParam,
             startParam,
             gotoEvent,
             focusEvent,
             validate)
{
    // check to make sure we are called only once:
    if (this._pathParam != (void 0))
      return;

    CollectionComponent.defineSubmit(eventParam, sourceParam);

    CollectionComponent.prototype._pathParam = pathParam;
    CollectionComponent.prototype._startParam = startParam;
    CollectionComponent.prototype._gotoEvent = gotoEvent;
    CollectionComponent.prototype._focusEvent = focusEvent;
    CollectionComponent.prototype._validate = validate;

    CollectionComponent.prototype.action = function(event,path,link)
    {
      this.addParam(this._pathParam,path);
      return this.submit(event, link);
    };

    CollectionComponent.prototype.range = function(path,start)
    {
      this.addParam(this._startParam, start);
      return this.action(this._gotoEvent, path);
    };

    CollectionComponent.prototype.focus = function(path,link)
    {
        return this.action(this._focusEvent, path, link);
    };
};

/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 * 
 *  http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */
////////////////////////////////////////////////////////////////////////////////
// The custom object for managing poll related services at the client.
//  Service provided currently is...
//  1. Store poll commands (usually the setTimeOut command), and the id
//  resulting out of activating (eval) it, keyed by the poll id (typically the  
//  id's of pollcomponents themselves)
//  2. Add and activate a poll command.
//  3. Reactivate all the poll commands that were set earlier.
////////////////////////////////////////////////////////////////////////////////
function _TrPollManager()
{
  this.pollIdList;
  this.active = true;
}

// Adds a command (replaces one that already exists with the same poll id)
//  and executes it right away.
_TrPollManager.prototype.addAndActivate = function(pollId, commandString, timeout)
{
  if (!this.pollIdList)
    this.pollIdList = new Array();
  this[pollId] = new _TrPollCommand(commandString, timeout, this.active);
  idIndex = -1;
  for (var i=0; i<this.pollIdList.length; i++)
  {
    if (pollId == this.pollIdList[i])
    {
      idIndex = i;
      break;
    }
  }
  if (idIndex != -1)
  {
    this.pollIdList[idIndex] == pollId;
  }
  else
  {
    this.pollIdList.push(pollId);
  }
}

// Deactivate all the registered poll commands.
_TrPollManager.prototype.deactivateAll = function()
{
  for (var i=0; i<this.pollIdList.length; i++)
  {
    clearTimeout(this[this.pollIdList[i]].commandId);
  }
  this.active = false;
}

// Reactivate all the registered poll commands.
_TrPollManager.prototype.reactivateAll = function()
{
  for (var i=0; i<this.pollIdList.length; i++)
  {
    this[this.pollIdList[i]].activate();
  }
  this.active = true;
}

////////////////////////////////////////////////////////////////////////////////
// Custom object representing a poll command.
////////////////////////////////////////////////////////////////////////////////
function _TrPollCommand(commandString, timeout, activate)
{
  this.commandString = commandString;
  this.timeout = timeout;
  if (activate)
    this.activate();
}

_TrPollCommand.prototype.activate = function()
{
  this.commandId = setTimeout(this.commandString, this.timeout);
}


/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 * 
 *  http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */

// External variables used:
//  _cfTransIconURL  - the transparent color swatch icon
//  _cfOpaqueIconURL - the opaque color swatch icon
//  _cfBgColor       - the background color of the page

var _cfBus = new Object();
var _cfTransIconURL;
var _cfOpaqueIconURL;
var _cfBgColor;

/*
 * Update the color field swatch.
 */
function _cfsw(
  colorField)
{
  var cff = _getColorFieldFormat(colorField);
  var swatchID = colorField.name + "$sw";
  var value = null;
  var swatch = _getElementById(document, swatchID);

  if (swatch != null)
  {
    var converterError = false;
    if (colorField.value != "")
    {      
      try
      {
        value = cff.getAsObject(colorField.value);
      }
      catch (e)
      {
        // no-op
      }
    }
    if (value != null)
    {
      if (value.alpha == 0)
      {
        swatch.style.backgroundColor = null;
        swatch.src = _cfTransIconURL;
        swatch.alt = _cfTrans;
      }
      else
      {
        swatch.style.backgroundColor = 
          new TrColorConverter("#RRGGBB").getAsString(value);
        swatch.src = _cfOpaqueIconURL;
        swatch.alt = cff.getAsString(value);
      }
    }
    else
    {
      swatch.style.backgroundColor = _cfBgColor;
      swatch.src = _cfOpaqueIconURL;
      swatch.alt = null;
    }

    // make sure tooltips are updated correctly
    // on Mozilla
    if (_agent.isGecko)
      swatch.title = swatch.alt;
  }
}

function _returnColorPickerValue(
  closingWindow,
  event
  )
{
  // extract the return value
  var newColor = closingWindow.returnValue;
  
  var colorField = closingWindow._colorField;

  // See below!
  if (colorField == null)
  {
    colorField = _savedColorField1879034;
  }

  //pu: Do not update if user dismissed color picker 
  // dialog without "Apply" ing the color
  if (closingWindow.isApplicable)
    _cfUpdate(colorField, newColor);
}

/*
 * ColorField Bus Select (used by ColorPalette)
 */
function _cfbs(
  event)
{
  _cfUpdate(_cfBus[event.source], event.params.value);
}

function _cfUpdate(
  colorField,
  newColor)
{
  if (colorField != null)
  {
    //
    // get the format to use to format the result
    //
    var cff = _getColorFieldFormat(colorField);
    
    // update the contents of the text field
    // or update hidden input for compact color field
    var isVisible = (colorField.type != 'hidden');
    var oldValue = colorField.value;
    var newValue = cff.getAsString(newColor);

    //pu: If transparent was not allowed, and the chosen color were to be
    // transparent, do not update the color field.
    if (newValue == _cfTrans && !cff._allowsTransparent)
      return;

    //pu: In case of blank selection from external picker, undefined newValue is 
    // legitimate, but "undefined" should not literally appear in the text area.
    if (newValue == null)
      newValue="";

    if (newValue != colorField.value)
    {
      // bug 2898744
      // also trigger onchange for compact mode
      // with only color swatch displayed
      // bug 2275982
      // trigger onchange programatically
      if (colorField.onchange != null)
      {
        // The IE event delivery mechanism for built-in events like onchange
        // automatically sets the window.event property for inspection by
        // event handlers.
        // When we synthesize this onchange event here, we must ensure that
        // window.event is as close as possible to the value of window.event
        // that would be visible to the onchange handler if it had been triggered 
        // by user interaction, like moving focus away from the field.
        // Updating window.event directly triggers a JS error in IE, so we must
        // use the built-in event delivery mechanism instead.
        // Here, we attach a handler to the IE-specific propertychange event,
        // which will fire when any property is changed, not just the value.
        // In our handler, we only respond to a change in the value property,
        // by detaching the handler and delivering the onchange event.
        // Now, the value for window.event during the onchange handler execution
        // is the property change event but it has the right values for source,
        // target, etc.
        // In all cases, the ordering for updates to the color field are as follows
        // 1. input value updated
        // 2. color swatch synchronized with new value (if present)
        // 3. onchange handler fires (if present)
        if (_agent.isIE)
        {
          // attach the value change listener
          colorField.onpropertychange = function()
          {
            var event = window.event;
            if (event.propertyName == 'value')
            {
              // detach the value change listener
              colorField.onpropertychange = function(){};
              
              // update swatch first to make sure it has correct
              // value before propertychange event delivery
              _cfsw(colorField);
       
              colorField.onchange(event);
            }
          }

          colorField.value = newValue;
        }
        else
        {
          colorField.value = newValue;

          // swatch not supported on NS47
          if (!_agent.isNav)
            _cfsw(colorField);
        
          var event = new Object();
          event.type = 'change';
          event.target = colorField;
          colorField.onchange(event);
        }
      }
      else // no onchange handler
      {
        colorField.value = newValue;

        // swatch not supported on NS47
        if (!_agent.isNav)
          _cfsw(colorField);
      }
    }
        
    if (isVisible)
    {
      colorField.select();
      colorField.focus();
    }
  }
}

/**
 * Private function for launching the color picker
 */
function _lcp(
  formName,
  nameInForm,
  destination
  )
{
  var colorField = document.forms[formName][nameInForm];

  // default the destination to the color picker dialog destination
  if (!destination)
  {
    destination = _jspDir + _getQuerySeparator(_jspDir) + "_t=fred&_red=cp";
  }
  else
  {
    // since we need to redirect, replace the last portion of the URL with
    // the redirect JSP
    var endOfUrl = destination.lastIndexOf('?');
    var urlArgs  = "";
  
    if (endOfUrl == -1)
    {
      endOfUrl = destination.length;
    }
    else
    {
      urlArgs = destination.substr(endOfUrl + 1);
    }

    var newDest = _jspDir + _getQuerySeparator(_jspDir);
    newDest += urlArgs;

    // add the correct first param separator
    newDest += _getQuerySeparator(newDest);
    newDest += "_t=fred";

    var redirect = destination.substring(0, endOfUrl);

    destination = newDest;

    // add in the redirect
    destination += "&redirect=" + escape(redirect);
  }
  
  var patterns = _cfs[nameInForm];
  var pattern = "#RRGGBB"
  if (patterns != null)
  {
    destination += "&pattern=";
    if (typeof patterns == "string")
    {
      pattern = patterns;
      destination += escape(pattern);
    }
    else
    {
      pattern = patterns[0];
      destination += escape(patterns.join(" "));
    }
  }

  // add the current color text
  if (colorField.value != "")
  {
    var format = _getColorFieldFormat(colorField);
    try
    {
      var color = format.getAsObject(colorField.value);
      if (color != null)
      {
        destination += "&value=";

        // escape due to '#' in color code, or possible spaces in _cfTrans
        if (color.alpha == 0)
          destination += escape(_cfTrans);
        else
          destination += escape(new TrColorConverter(pattern).getAsString(color));
      }
    }
    catch (e)
    {
      // no-op 
    }
  }

  var allowsTransparent = _cfts[nameInForm];
  if (allowsTransparent != null)
  {
    destination += "&allowsTransparent=" + allowsTransparent;
  }

  // add the locale
  destination += "&loc=" + _locale;
    
  // and the character set encoding
  if (window["_enc"])
  {
    destination += "&enc=" + _enc;
  }

  var colorDialog = openWindow(self,
                             destination,
                             'colorDialog',
                             {width:430, height:230},
                             true,
                             null,
                             _returnColorPickerValue);
  
  // save the date field on the calendar window for access
  // from event handler
  colorDialog._colorField = colorField;

  // And, for bug 1879034, stash it on a JS variable.  It
  // seems that IE sometimes has already blown away the values
  // on "colorDialog"!
  _savedColorField1879034 = colorField;  
}

var _savedColorField1879034;

/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 * 
 *  http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */
function _getColorFieldFormat(
  colorField)
{
  var name = colorField.name;
  if (name && _cfs)
  {
    var format = _cfs[name];
    var trans  = _cfts[name];
    if (format || trans)
      return new TrColorConverter(format, trans);
  }

  return new TrColorConverter();
}

function _fixCFF(
  colorField)
{
  var format = _getColorFieldFormat(colorField);

  if (colorField.value != "")
  {
    try
    {
      var value = format.getAsObject(colorField.value);
      if (value != null)
        colorField.value = format.getAsString(value);
    }
    catch(e)
    {
    }      
  }
}


/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 * 
 *  http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */

/**
 * Construct a TrColorConverter with the specifed color pattern.
 */
function TrColorConverter(
  pattern,
  allowsTransparent,
  patternsString,
  messages)
{
  // for debugging
  this._class = "TrColorConverter";
  this._allowsTransparent = allowsTransparent;  
  this._patternsString = patternsString;     
  this._messages = messages;
  
  if (pattern != null)
  {
    if (typeof(pattern) == "string" ) 
      pattern = [pattern];
  }

  this._pattern = pattern;
}

TrColorConverter.prototype = new TrConverter();

TrColorConverter.prototype.getFormatHint = function()
{
	if(this._messages && this._messages["hint"])
	{
    return TrMessageFactory.createCustomMessage(
      this._messages["hint"],
      this._pattern);
	}
	else
	{
    return TrMessageFactory.createMessage(
      "org.apache.myfaces.trinidad.convert.ColorConverter.FORMAT_HINT",
      this._pattern);
	}
}
TrColorConverter.prototype.getAsString = function(
  formatColor)
{
  //pu: Return undefined string for an undefined Color.
  if (formatColor == null)
    return null;

  // return localized transparent text for transparent color
  if (formatColor.alpha == 0)
    return _cfTrans;

  var stringHolder = new Object();
  stringHolder.value ="";
  
  var pattern = this._pattern;
  if (typeof pattern != "string")
    pattern = pattern[0];
    
  _cfoDoClumping(pattern,
              _cfoSubformat,
              formatColor,
              stringHolder);
  
  return stringHolder.value;
}

/**
 * Parses a String into a Color using the current object's pattern.  If the
 * parsing fails, undefined will be returned.
 */
TrColorConverter.prototype.getAsObject  = function(
  parseString,
  label)
{
  // The following are from the javadoc for Number and DateTimeConverter, same applies to color....
  // If the specified String is null, return a null. Otherwise, trim leading and trailing whitespace before proceeding.
  // If the specified String - after trimming - has a zero length, return null.
  if (parseString == null)
    return null;
    
  parseString = TrUIUtils.trim(parseString);
  if (parseString.length == 0)
    return null

  // return transparent color for localized transparent text
  if (this._allowsTransparent && _cfTrans == parseString)
    return new TrColor(0,0,0,0);
     
  var facesMessage;
  var key = "org.apache.myfaces.trinidad.convert.ColorConverter.CONVERT";
  if(this._messages && this._messages["detail"])
  {
    facesMessage = _createCustomFacesMessage(
                                       TrMessageFactory.getSummaryString(key),
                                       this._messages["detail"],
                                       label,
                                       parseString,
                                       this._patternsString);
  }
  else
  {
    facesMessage = _createFacesMessage(key,
                                       label,
                                       parseString,
                                       this._patternsString);
  }
  
  
  var pattern = this._pattern;                                       
  if (typeof pattern == "string")
  {
    return this._rgbColorParseImpl(parseString,
                              pattern,
                              facesMessage);
  }
  else
  { 
    var i;
    for (i = 0; i < pattern.length; i++)
    {
      try{
        var color = this._rgbColorParseImpl(parseString,
                                     pattern[i],
                                     facesMessage);
        return color;
      }
      catch (e)
      {
        // if we're not on the last pattern try the next one, 
        // but if we're on the last pattern, throw the exception
        if ( i == pattern.length-1)
          throw e;
      }
    }
  }
}

TrColorConverter.prototype._rgbColorParseImpl  = function(
  parseString,
  parsePattern,
  msg)
{
  var parseContext = new Object();
  parseContext.currIndex = 0;
  parseContext.parseString = parseString;
  parseContext.parseException = new TrConverterException(msg);
  
  var parsedColor = new TrColor(0x00, 0x00, 0x00);

  // parse the color
  if (_cfoDoClumping(parsePattern,
                  _cfoSubParse,
                  parseContext,
                  parsedColor))
  {
    if (parseString.length != parseContext.currIndex)
    {
      throw parseContext.parseException;
    }

    return parsedColor;
  }
  else
  {
    // failure
    throw parseContext.parseException;
  }
}

function TrColor(
  red,
  green,
  blue,
  alpha)
{
  // for debugging
  this._class = "TrColor";
  
  if (alpha == null)
    alpha = 0xff;

  this.red   = (red & 0xff);
  this.green = (green & 0xff);
  this.blue  = (blue & 0xff);
  this.alpha = (alpha & 0xff);
}

TrColor.prototype.toString = function()
{
  return "rgba(" + this.red + 
         "," + this.green + 
         "," + this.blue + 
         "," + this.alpha + ")";
}




// External variables used:
//  _cfTrans    - the localized text for transparent colors

var _cfTrans;


/**
 * Clump up similar runs of pattern characters from the format patter and
 * call the subfunction for each result.  Return whether the clumping
 * succeeded.
 */
function _cfoDoClumping(
  formatPattern,
  subFunction,
  param,
  outValue
  )
{  
  var formatLength = formatPattern.length;
  var inQuote      = false;
  var kindCount    = 0;
  var lastChar     = null;
  var startIndex   = 0;
  
  for (var i = 0; i < formatLength; i++)
  {
    var currChar = formatPattern.charAt(i);
    
    if (inQuote)
    {
      if (currChar == "\'")
      {
        inQuote = false;
        
        // handle to single quotes in a row as escaping the quote
        // by not skipping it when outputting
        if (kindCount != 1)
        {
          startIndex++;
          kindCount--;
        }

        // output the quoted text
        if (!subFunction(formatPattern,
                         "\'",
                         startIndex,
                         kindCount,
                         param,
                         outValue))
        {
          // failure
          // alert("failure at " + startIndex + " with " + lastChar);
          return false;
        }
        
        kindCount = 0;
        lastChar  = null;
      }
      else
      {
        // keep adding characters to the escaped String
        kindCount++;
      }
    }
    else
    {
      // the characters that we are collecting have changed
      if (currChar != lastChar)
      {
        if (kindCount != 0)
        {       
          // output the previously collected string
          if (!subFunction(formatPattern,
                           lastChar,
                           startIndex,
                           kindCount,
                           param,
                           outValue))
          {
            // failure
            //alert("failure at " + startIndex + " with " + lastChar);
            return false;
          }
          
          kindCount = 0;
          lastChar  = null;
        }
        
        if (currChar == '\'')
        {
          inQuote = true;
        }
  
        startIndex = i;      
        lastChar = currChar;
      }

      // keep collecting this kind of character together
      kindCount++;
    }   
  }
  
  // output any left over substring being collected
  if (kindCount != 0)
  {
    if (!subFunction(formatPattern,
                     lastChar,
                     startIndex,
                     kindCount,
                     param,
                     outValue))
    {
      // failure
      //alert("failure at " + startIndex + " with " + lastChar);
      return false;
    }
  }
  
  // success
  return true;
}


/**
 * Format a clump of pattern elements using the specified time.
 */
function _cfoSubformat(
  inString,
  formatType,
  startIndex,
  charCount,
  color,
  stringHolder
  )
{    

  // string to append to the toString
  var appendString = null;
  
  if ((formatType >= 'A') && (formatType <= 'Z') ||
      (formatType >= 'a') && (formatType <= 'z'))
  {
    switch (formatType)
    {
      case 'r': // decimal red component (0-255)
        appendString = _cfoGetPaddedNumber(color.red, charCount, 3, 10);
        break;
      
      case 'g': // decimal green component (0-255)
        appendString = _cfoGetPaddedNumber(color.green, charCount, 3, 10);
        break;
      
      case 'b': // decimal blue component (0-255)
        appendString = _cfoGetPaddedNumber(color.blue, charCount, 3, 10);
        break;
      
      case 'a': // decimal alpha component (0-255)
        appendString = _cfoGetPaddedNumber(color.alpha, charCount, 3, 10);
        break;
      
      case 'R': // hex red component (0x00-0xff)
        appendString = 
          _cfoGetPaddedNumber(color.red, charCount, 2, 16).toUpperCase();
        break;
      
      case 'G': // hex green component (0x00-0xff)
        appendString = 
          _cfoGetPaddedNumber(color.green, charCount, 2, 16).toUpperCase();
        break;
      
      case 'B': // hex blue component (0x00-0xff)
        appendString = 
          _cfoGetPaddedNumber(color.blue, charCount, 2, 16).toUpperCase();
        break;
      
      case 'A': // hex alpha component (0x00-0xff)
        appendString = 
          _cfoGetPaddedNumber(color.alpha, charCount, 2, 16).toUpperCase();
        break;
      
      default:
        // do nothing rather than throw an exception
        appendString = "";
    }
  }
  else
  {
    // all other results are literal
    appendString = inString.substring(startIndex, startIndex + charCount);
  }
  
  stringHolder.value += appendString;
  
  // formatting should never fail
  return true;
}


/**
 * Parse a substring using a clump of format elements.
 */
function _cfoSubParse(
  inString,
  formatType,
  startIndex,
  charCount,
  parseContext,
  parsedColor
  )
{
  // Start index of the string being parsed (as opposed
  // to startIndex, which is the index on the format mask)
  var inStartIndex = parseContext.currIndex;


  if ((formatType >= 'A') && (formatType <= 'Z') ||
      (formatType >= 'a') && (formatType <= 'z'))
  {
    switch (formatType)
    {
      case 'r': // decimal red component (0-255)
        parsedColor.red = _cfoAccumulateNumber(parseContext, charCount, 3, 10);
        if (parsedColor.red == null)
        {
          return false;
        }
        break;
      
      case 'g': // decimal green component (0-255)
        parsedColor.green = _cfoAccumulateNumber(parseContext, charCount, 3, 10);
        if (parsedColor.green == null)
        {
          return false;
        }
        break;
      
      case 'b': // decimal blue component (0-255)
        parsedColor.blue = _cfoAccumulateNumber(parseContext, charCount, 3, 10);
        if (parsedColor.blue == null)
        {
          return false;
        }
        break;
      
      case 'a': // decimal alpha component (0-255)
        parsedColor.alpha = _cfoAccumulateNumber(parseContext, charCount, 3, 10);
        if (parsedColor.alpha == null)
        {
          return false;
        }
        break;
      
      case 'R': // hex red component (0x00-0xff)
        parsedColor.red = _cfoAccumulateNumber(parseContext, charCount, 2, 16);
        if (parsedColor.red == null)
        {
          return false;
        }
        break;
      
      case 'G': // hex green component (0x00-0xff)
        parsedColor.green = _cfoAccumulateNumber(parseContext, charCount, 2, 16);
        if (parsedColor.green == null)
        {
          return false;
        }
        break;
      
      case 'B': // hex blue component (0x00-0xff)
        parsedColor.blue = _cfoAccumulateNumber(parseContext, charCount, 2, 16);
        if (parsedColor.blue == null)
        {
          return false;
        }
        break;
      
      case 'A': // hex alpha component (0x00-0xff)
        parsedColor.alpha = _cfoAccumulateNumber(parseContext, charCount, 2, 16);
        if (parsedColor.alpha == null)
        {
          return false;
        }
        break;
      
      default:
    }
  }
  else
  {
    // consume constants
    return _cfoMatchText(parseContext,
                      inString.substring(startIndex, startIndex + charCount));
  }
  
  // match succeeded
  return true;
}


/**
 * Match the specified text in a case insensitive manner,
 * returning true and updating the
 * <code>parseContext</code> if the match succeeded.
 */
function _cfoMatchText(
  parseContext,
  text
  )
{
  // if no text to match then match will fail
  if (!text)
    return false;

  // get the length of the text to match
  var textLength  = text.length;

  var currIndex   = parseContext.currIndex;
  var parseString = parseContext.parseString;
  
  // determine whether we have enough of the parseString left to match
  if (textLength > parseString.length - currIndex)
  {
    return false;
  }

  var parseText = parseString.substring(currIndex, currIndex + textLength);
  
  if (parseText != text)
    return false;
    
  // update the current parseContext
  parseContext.currIndex += textLength;
  
  return true;
}
 

/**
 * Accumlates and returns a number at this location or undefined, if
 * there is no number.
 */
function _cfoAccumulateNumber(
  parseContext,
  minDigits,
  maxDigits,
  base)
{
  var startIndex  = parseContext.currIndex;
  var currIndex   = startIndex;
  var parseString = parseContext.parseString;
  var parseLength = parseString.length;
  if (parseLength > currIndex + maxDigits)
    parseLength = currIndex + maxDigits;

  var currValue = 0;

  // gather up all of the digits
  while (currIndex < parseLength)
  {
    var currDigit = parseInt(parseString.charAt(currIndex), base);

    if (!isNaN(currDigit))
    {
      // add on the digit and shift over the results
      currValue *= base;
      currValue += currDigit;

      currIndex++;
    }
    else
    {
      break;
    }
  }

  if (startIndex != currIndex && 
      (currIndex - startIndex) >= minDigits)
  {
    // update the current parseContext
    parseContext.currIndex = currIndex;

    // return the numeric version
    return currValue;
  }
  else
  {
    // no number at this location
    return null;
  }
}


/**
 * Pad out a number with leading 0's to meet the minDigits digits or
 * truncate to meet the maxDigits.
 */
function _cfoGetPaddedNumber(
  number,
  minDigits,
  maxDigits,
  base)
{  
  var stringNumber = number.toString(base);
  
  //
  // pad out any number strings that are too short
  //
  if (minDigits != null)
  {    
    var addedDigits = minDigits - stringNumber.length;
  
    while (addedDigits > 0)
    {
      stringNumber = "0" + stringNumber;
      addedDigits--;
    }
  }
  
  //
  // truncate any number strings that are too long
  //
  if (maxDigits != null)
  {
    var extraDigits = stringNumber.length - maxDigits;
    
    if (extraDigits > 0)
    {
      stringNumber = stringNumber.substring(extraDigits,
                                            extraDigits + maxDigits);
    }
  }
  
  return stringNumber;
}
/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 * 
 *  http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */

//FIXME: grab from a translation JS
var _shuttle_no_name = "You must supply the shuttle's name to create a proxy";
var _shuttle_no_form_name_provided = "A form name must be provided";
var _shuttle_no_form_available = "This shuttle is not in a form";


//========================================================================
//
// (Public) TrShuttleProxy Object
//

// TrShuttleProxy instances can be used to determine information about
// a shuttle at runtime and gain access to the data it contains.
// A TrShuttleProxy can be constructed from just the name of the shuttle,
// or the shuttle name and its form name. If the form name is not
// supplied, it will be determined at runtime.


// Constructor
function TrShuttleProxy(
  shuttleName,
  formName
  )
{
  if (shuttleName == (void 0))
  {
    alert(_shuttle_no_name);
    this.shuttleName = "";
    this.formName = "";
    return;
  }

  this.shuttleName = shuttleName;

  this.formName = "";
  // get the form name
  if (formName == (void 0))
  {
    // find the first form with this shuttle
    var formsLength = document.forms.length;
    var listName = shuttleName + ":leading";

    for (var i = 0; i < formsLength; i++)
    {
      if (document.forms[i][listName] != (void 0))
      {
        this.formName = _getFormName(document.forms[i]);
        break;
      }
    }
    if(this.formName == "")
    {
      alert(shuttle_no_form_available);
      return;
    }
  }
  else
  {
    this.formName = formName;
  }
}

//
// Public prototype method hookups
//

//
// Calling getItems(boolean leadingList) on a TrShuttleProxy will return an
// array of the items in the given list of the shuttle
//
TrShuttleProxy.prototype.getItems = function(
  leadingList
  )
{
  //default params not given
  if(leadingList == (void 0))
  {
    leadingList = true;
  }

  //get the list name
  var listName = TrShuttleProxy._getListName(this.shuttleName, leadingList);


  var list = document.forms[this.formName].elements[listName];

  var items = new Array();
  //length - 1 because of bars
  for(var i=0; i<list.length-1; i++)
  {
    items[i] = list.options[i];
  }

  return items;
};

//
// Calling getSelectedItems(boolean leadingList) on a TrShuttleProxy will
// return an array of the selected items in the given list of the shuttle
//
TrShuttleProxy.prototype.getSelectedItems = function(
  leadingList
  )
{
  //default params not given
  if(leadingList == (void 0))
  {
    leadingList = true;
  }

  var listName = TrShuttleProxy._getListName(this.shuttleName, leadingList);


  var list = document.forms[this.formName].elements[listName];

  var items = new Array();
  var j = 0;
  //length - 1 because of bars
  for(var i=0; i<list.length-1; i++)
  {
    if(list.options[i].selected)
    {
      items[j] = list.options[i];
      j++;
    }
  }

  return items;
};

//
// Calling getItemCount(boolean leadingList) on a TrShuttleProxy will
// return the number of items in the given list of the shuttle
//
TrShuttleProxy.prototype.getItemCount = function(
  leadingList
  )
{
  //default params not given
  if(leadingList == (void 0))
  {
    leadingList = true;
  }

  var listName = TrShuttleProxy._getListName(this.shuttleName, leadingList);


  //minus 1 for bars
  return document.forms[this.formName].elements[listName].length - 1;
};


//
// Calling getSelectedItemCount(boolean leadingList) on a TrShuttleProxy will
// return the number of selected items in the given list of the shuttle
//
TrShuttleProxy.prototype.getSelectedItemCount = function(
  leadingList
)
{
  //default params not given
  if(leadingList == (void 0))
  {
    leadingList = true;
  }

  var listName = TrShuttleProxy._getListName(this.shuttleName, leadingList);


  var list = document.forms[this.formName].elements[listName];

  var j = 0;
  //length - 1 because of bars
  for(var i=0; i<list.length-1; i++)
  {
    if(list.options[i].selected)
    {
       j++;
    }
  }

  return j;
};

//
// Calling addItem(boolean leadingList, int index, string text, string value, string description)
// on a TrShuttleProxy will insert a new option into the given list at the
// specified index with the specified text, value, and description.
//
TrShuttleProxy.prototype.addItem = function(
  leadingList,
  index,
  text,
  value,
  description
  )
{
  //default params not given
  if(value == (void 0))
  {
    value = "";
  }
  if(text == (void 0))
  {
    text = "";
  }
  if(description == (void 0))
    {
      description = "";
  }
  if(leadingList == (void 0))
  {
    leadingList = true;
  }


  //get the list
  var listName = TrShuttleProxy._getListName(this.shuttleName, leadingList);



  //get an appropriate index
  if(index == (void 0))
  { //minus 1 for bars
    index = document.forms[this.formName].elements[listName].length - 1;
  }

  if(index < 0)
  {
    index = 0;
  }

  //minus 1 for bars
  if(index > document.forms[this.formName].elements[listName].length - 1)
  {
    index = document.forms[this.formName].elements[listName].length - 1;
  }

  //first move all items at that index and below down to make room for this
  //new item.
  var theList = document.forms[this.formName].elements[listName];

  //make a new option for the bars
  theList.options[theList.length] =
       new Option(theList.options[theList.length-1].text,
                  theList.options[theList.length-1].value,
                  false,
                  false);

  //move items down
  for(var i = theList.length - 1; i > index; i--)
  {
    theList.options[i].text = theList.options[i-1].text;
    theList.options[i].value = theList.options[i-1].value;
    theList.options[i].selected = theList.options[i-1].selected;
  }

  //insert the new item
  theList.options[index].text = text;
  theList.options[index].value = value;
  theList.options[index].selected = false;

  // add description
  var descArray = TrShuttleProxy._getDescArray(listName);
  TrShuttleProxy._addDescAtIndex( descArray, description, index);

  TrShuttleProxy._makeList(this.formName, listName);
};

//
// Calling deleteItemByValue(boolean leadingList, string value)
// on a TrShuttleProxy will delete the option with the given value
// from the given list.
//
TrShuttleProxy.prototype.deleteItemByValue = function(
  leadingList,
  value
  )
{
  if(value == (void 0))
  {
    return;
  }


  //get the list
  var listName = TrShuttleProxy._getListName(this.shuttleName, leadingList);
  var theList = document.forms[this.formName].elements[listName];

  for(var i=0; i<theList.length-1; i++)
  {
    var val = theList.options[i].value;
    if(val == value)
    {
      var descArray = TrShuttleProxy._getDescArray( listName );
      TrShuttleProxy._deleteDescAtIndex( descArray, i);
      TrShuttleProxy._clearDescAreas(this.formName, listName);

      theList.options[i] = null;
      TrShuttleProxy._makeList(this.formName, listName);

      return;
    }
  }

};


//
// Calling deleteSelectedItems(boolean leadingList)
// on a TrShuttleProxy will delete all selected items from the given list
//
TrShuttleProxy.prototype.deleteSelectedItems = function(
  leadingList
  )
{
  if(leadingList == (void 0))
  {
    leadingList = true;
  }


  //get the list
  var listName = TrShuttleProxy._getListName(this.shuttleName, leadingList);
  var theList = document.forms[this.formName].elements[listName];

  var selIndexes = TrShuttleProxy._getSelectedIndexes(this.formName,listName);

  for(var i = selIndexes.length; i >=0; i--)
  {
    theList.options[selIndexes[i]] = null;
  }

  var descArray = TrShuttleProxy._getDescArray(listName);

  TrShuttleProxy._deleteDescAtIndexes( descArray, selIndexes);

  TrShuttleProxy._clearDescAreas(this.formName, listName);

  TrShuttleProxy._makeList(this.formName, listName);
};

//
// Calling move(boolean fromLeadingList, boolean allItems) on a TrShuttleProxy
// will move the selected items, or all items depending on allItems parameter,
// from the given list to the other list.
//
TrShuttleProxy.prototype.move = function(
  fromLeadingList,
  allItems
  )
{
  //default parameters not given
  if(allItems == (void 0))
  {
    allItems = false;
  }
  if(fromLeadingList == (void 0))
  {
    fromLeadingList = true;
  }

  //get the list names
  var fromListName = TrShuttleProxy._getListName(this.shuttleName, fromLeadingList);
  var toListName = TrShuttleProxy._getListName(this.shuttleName, !fromLeadingList);

  //move the items
  if(allItems)
  {
    TrShuttleProxy._moveAllItems(fromListName, toListName, this.formName);
  }
  else
  {
    TrShuttleProxy._moveItems(fromListName, toListName, this.formName);
  }
};

//
// Calling reorderList(boolean down, boolean allTheWay, boolean leadingList) on
// a TrShuttleProxy will move the selected items in the second list of the proxy
//in the direction specified.  If allTheWay is true it will move the items all
// the way to the top or bottom.  Otherwise the items move one slot.
//
TrShuttleProxy.prototype.reorderList = function(
  down,
  allTheWay,
  leadingList
)
{
  //default params not given
  if(leadingList == (void 0))
  {
    leadingList = true;
  }
  if(allTheWay == (void 0))
  {
    allTheWay = false;
  }
  if(down == (void 0))
  {
    down = false;
  }

  //get the listName
  var listName = TrShuttleProxy._getListName(this.shuttleName, leadingList);



  //reorder the list
  if(!allTheWay)
  {
    TrShuttleProxy._orderList(down, listName, this.formName);
  }
  else
  {
    TrShuttleProxy._orderTopBottomList(down, listName, this.formName);
  }
};


//
// Calling reset will reset the shuttle to its initial state.
//
TrShuttleProxy.prototype.reset = function()
{
  TrShuttleProxy._resetItems( this.shuttleName, this.formName);
};



/*===========================================================================*/
/*-----------------------------------------------------------------------
 *PRIVATE SHUTTLE METHODS
 *-----------------------------------------------------------------------*/


/*
 * _remove
 *
 * This function removes the number of elements specified
 * by deleteCount from an array starting at the index given
 * by start
 */

TrShuttleProxy._remove = function( array, start, deleteCount )
{

  var len = array.length;

  if (deleteCount > len)
    return;

  for ( var i = start; i < len ; i++)
  {

    if ( i < len - deleteCount )
      array[i] = array[ i + deleteCount];
    else
      array[ i ] = void 0;
  }

  array.length = len - deleteCount;

}


/*
 * _displayDesc
 *
 * Displays the description in the description area below the list.
 *
 */
TrShuttleProxy._displayDesc = function(
  listName,
  formName
  )
{


  if(formName == (void 0))
  {
    alert(_shuttle_no_form_name_provided);
    return;
  }


  if(formName.length == 0)
  {
    alert(shuttle_no_form_available);
    return;
  }

  // the textInput where descriptions are displayed
  var descArea = document.forms[formName].elements[ listName + ':desc'];

  if( descArea == void(0))
  {
    return;
  }


  // the array of descriptions
  var descArray  =  TrShuttleProxy._getDescArray( listName );

  if( descArray == (void 0) || descArray.length == 0)
  {
    return;
  }

  //get the indexes of the selected items
  var selItems = TrShuttleProxy._getSelectedIndexes(formName, listName);

  //if no items are selected, return
  if(selItems.length == 0)
  {
    descArea.value = "";
    TrShuttleProxy._setSelected( listName, selItems );
    return;
  }

  // get the last description selected
  var selOptDesc = TrShuttleProxy._getSelectedDesc( listName, descArray, selItems );

  // set the value of the description area to be the last selected item
  descArea.value = selOptDesc;

  // set which items are currently selected
  TrShuttleProxy._setSelected( listName, selItems );


}


/*
 * _getDescArray
 *
 * This function gets the description array
 */

TrShuttleProxy._getDescArray = function
(
  listName
)
{
  var descArray = window[listName.replace(/:/g,'_') + '_desc'];
  return descArray;

}

/*
 * _getSelectedDesc
 *
 * This function gets the last selected description. If a
 * user selects items using the control key, and they select
 * the item at index 4, then 10, and then 6, the descriptions
 * for 4, then 10, then 6 should be displayed.
 *
 * There is a local variable where what was previously selected
 * is kept and this is compared to what is currently selected.
 * These are compared to determine the last item selected.
 */

TrShuttleProxy._getSelectedDesc = function
(
  listName,
  descArray,
  selItems
)
{

  // get the array of the indexes of previously selected items
  var prevSelArray = TrShuttleProxy._getSelectedArray( listName );

  // if only one item is currently selected return its description
  if ( selItems.length == 1 )
    return descArray[selItems[0]];


  // if the difference between the number of items
  // previously selected and currently selected is
  // not equal to one return no description, otherwise
  // it is unclear which description to display
  if ( selItems.length - prevSelArray.length != 1 )
    return "";


  // find the index now selected that was not
  // previously selected and return the description
  // for the item at that index.
  for ( var i = 0; i < selItems.length; i++ )
  {
    if ( i >= prevSelArray.length || prevSelArray[i] != selItems[i] )
      return descArray[selItems[i]];
  }

  // return an empty string if all else fails
  return "";
}

/*
 * _getSelectedArray
 *
 * This function gets the array of indexes of
 * previously selected items
 *
 */


TrShuttleProxy._getSelectedArray = function
(
  listName
)
{
  var selected = window[listName.replace(/:/g,'_') + '_sel'];
  return selected;
}

/*
 * _setSelected
 *
 * This function sets the array of indexes of
 * previously selected items
 *
 */

TrShuttleProxy._setSelected = function
(
  listName,
  selected
)
{

  var selectedArray = TrShuttleProxy._getSelectedArray( listName );

  if ( selectedArray != (void 0) )
  {
    var len = selectedArray.length;
    TrShuttleProxy._remove( selectedArray, 0, len);

    for ( var i = 0; i < selected.length; i++ )
    {
      selectedArray[i] = selected[i];
    }

  }

}


/*
 * _addDescAtIndex
 *
 * This function adds a description at a given index.
 */

TrShuttleProxy._addDescAtIndex = function
(
  descArray,
  addedDesc,
  index
)
{


  if ( descArray != (void 0 ) )
  {
    var len = descArray.length;

    for ( var i = len - 1 ; i >= index; i-- )
    {
      descArray[i + 1] = descArray[i];
    }

    descArray[index] = addedDesc;
    descArray.length = len + 1;
  }
}

/*
 * _deleteDescAtIndex
 *
 * This function removes a description at a given index.
 */

TrShuttleProxy._deleteDescAtIndex = function
(
  descArray,
  index
)
{
  if ( descArray != (void 0 ))
    TrShuttleProxy._remove(descArray, index, 1);
}

/*
 * _deleteDescAtIndexes
 *
 * This function removes descriptions at given indexes.
 */

TrShuttleProxy._deleteDescAtIndexes = function
(
  descArray,
  indexes
)
{
  if ( descArray != (void 0 ))
  {
    for ( var i = indexes.length - 1; i >= 0; i--)
    {
      TrShuttleProxy._remove(descArray, indexes[i], 1);
    }
  }
}


/*
 * _deleteDescAtIndexes
 *
 * Sets the textInput areas displaying descriptions to
 * the empty string.
 */

TrShuttleProxy._clearDescAreas = function(
  formName,
  list1,
  list2
)
{
  // move descriptions and clear description area
  var descArea1 = document.forms[formName].elements[ list1 + ':desc'];
  var descArea2 = document.forms[formName].elements[ list2 + ':desc'];

  if( descArea1 != void(0))
  {
    descArea1.value = "";
  }

  if( descArea2 != void(0))
  {
    descArea2.value = "";
  }
}



/*
 * _moveItems
 *
 * This function moves the selected items in the 'from' list to the
 * 'to' list.  If no formName is supplied, the form is found when
 * this is called.  The items are inserted in the 'to' list
 * at the bottom. The 'from' and 'to' parameters should be the
 * list names(i.e.  "<shuttleName>:leading" or "<shuttleName>:trailing")
 */
TrShuttleProxy._moveItems = function(
  from,
  to,
  formName
  )
{
  //get the formName if needed
  if(formName == (void 0))
  {
    formName = TrShuttleProxy._findFormNameContaining(from);
  }

  if(formName.length == 0)
  {
    alert(shuttle_no_form_available);
    return;
  }

  //store the from and to lists
  var fromList = document.forms[formName].elements[from];
  var toList = document.forms[formName].elements[to];

  if ( fromList == (void 0 ) || toList == (void 0 ))
    return;


  //get all the indexes of the selected items
  var selItems = TrShuttleProxy._getSelectedIndexes(formName, from);

  //if no items are selected, return with alert.
  if(selItems.length == 0)
  {
    if (_shuttle_no_items_selected.length > 0)
      alert(_shuttle_no_items_selected);

    return;
  }


  var fromDescArray = TrShuttleProxy._getDescArray(from);
  var toDescArray = TrShuttleProxy._getDescArray(to);

  //set no selection on toList so it will only have new items selected.
  toList.selectedIndex = -1;

  //get the index in the toList to start inserting at.  Length-1 because of
  //bars.
  var insertAt = toList.length-1;

  //save bar text so you know how long it should be
  var barText = toList.options[insertAt].text;

  //insert the items at the end of the toList
  for(var i=0; i<selItems.length; i++)
  {
    var oText = fromList.options[selItems[i]].text;
    var oValue = fromList.options[selItems[i]].value;

    if(i == 0)
    { //replace the bars
      toList.options[insertAt].text = oText;
      toList.options[insertAt].value = oValue;
    }
    else
    {  //have to make new item
      toList.options[insertAt] = new Option(oText, oValue, false, false);
    }

    if ( toDescArray != (void 0) && fromDescArray != (void 0) )
      toDescArray[insertAt] = fromDescArray[selItems[i]];

    toList.options[insertAt].selected = true;
    insertAt++;
  }

  //insert a new bar at bottom of toList
  toList.options[insertAt] = new Option(barText, "", false, false);
  toList.options[insertAt].selected = false;

  //remove items from fromList.  do this backward to maintain indices
  for( var i=selItems.length-1; i >= 0; i--)
  {
    if ( fromDescArray != (void 0) )
      TrShuttleProxy._remove( fromDescArray, selItems[i], 1 );
    fromList.options[selItems[i]] = null;
  }

  //make no selected on fromList
  fromList.selectedIndex = -1;

  TrShuttleProxy._clearDescAreas( formName, from);
  TrShuttleProxy._displayDesc( to, formName );

  //make the new lists for submitting.
  TrShuttleProxy._makeList(formName, from);
  TrShuttleProxy._makeList(formName, to);
}

/*
 * _moveAllItems
 *
 * This function moves all the items in the 'from' list to the
 * 'to' list.  If no formName is supplied, the form is found when
 * this is called.  The items are inserted in the 'to' list
 * at the bottom. The 'from' and 'to' parameters should be the
 * list names(i.e.  "<shuttleName>:leading" or "<shuttleName>:trailing")
 */
TrShuttleProxy._moveAllItems = function(
  from,
  to,
  formName
  )
{
  //get the formName is needed
  if(formName == (void 0))
  {
    formName = TrShuttleProxy._findFormNameContaining(from);
  }

  //get the lists
  var fromList = document.forms[formName].elements[from];
  var toList = document.forms[formName].elements[to];

  //save the bar text for later use.
  var barText =
    toList.options[document.forms[formName].elements[to].length-1].text

  //get the index to start inserting at in the toList.  length-1 because of
  //bars
  var insertAt = toList.length-1;
  var fromDescArray = TrShuttleProxy._getDescArray(from);
  var toDescArray = TrShuttleProxy._getDescArray(to);

  //move the items
  if (fromList.length > 1)
  {
    //move all but the last (bars).
    var initialLength = fromList.length
    for(var i=0; i<initialLength-1; i++)
    {
      var oText = fromList.options[0].text;
      var oValue = fromList.options[0].value;
      fromList.options[0] = null;
      if(i == 0)
      { //replace the bars
        toList.options[insertAt].text = oText;
        toList.options[insertAt].value = oValue;
      }
      else
      { //make new option
        toList.options[insertAt] = new Option (oText,oValue,false,false);

      }

      if ( toDescArray != (void 0) && fromDescArray != (void 0) )
        toDescArray[insertAt] = fromDescArray[i];

      insertAt++;
    }

    //insert a new bar:
    toList.options[insertAt] = new Option(barText, "", false, false);
    toList.options[insertAt].selected = false;

    if ( fromDescArray != (void 0) )
    {
      var len = fromDescArray.length;
      TrShuttleProxy._remove(fromDescArray, 0, len);
    }

    //set no selection on both lists
    fromList.selectedIndex = -1;
    toList.selectedIndex = -1;

    TrShuttleProxy._clearDescAreas( formName, from, to );


    //make the lists for submission
    TrShuttleProxy._makeList(formName, from);
    TrShuttleProxy._makeList(formName, to);
  }
  else if (_shuttle_no_items.length > 0)
  {
    alert(_shuttle_no_items);
  }
}

/*
 * _orderList
 *
 * This function reorders the given list by shifting the selections in
 * the given direction.  If no formName is supplied, the form is found when
 * this is called. The 'list' parameter should be the
 * list name(i.e.  "<shuttleName>:leading" or "<shuttleName>:trailing")
 */
TrShuttleProxy._orderList = function(
  down,
  list,
  formName
  )
{
  //get the formName if needed
  if(formName == (void 0))
  {
    formName = TrShuttleProxy._findFormNameContaining(list);
  }

  //get the actual list
  var colList = document.forms[formName].elements[list];

  //get all the selected item indexes
  var selItems = TrShuttleProxy._getSelectedIndexes(formName, list);

  //if no items are selected, return with alert.
  if(selItems.length == 0)
  {
    if (_shuttle_no_items_selected.length > 0)
      alert(_shuttle_no_items_selected);

    return;
  }

  var descArray = TrShuttleProxy._getDescArray(list);

  // Start with the last selected index and move up, working by blocks
  var processed = selItems.length - 1;
  while (processed >= 0)
  {
    var lastInBlock = selItems[processed];
    var firstInBlock = lastInBlock;

    var tempIndex = processed;


    // find the first index in that block
    while ((tempIndex > 0) && ((selItems[tempIndex] -
                                selItems[tempIndex - 1]) == 1))
    {
      tempIndex--;
      firstInBlock--;
    }

    if (down == 0)
    {
      // move this block up
      // if we are at the top, do nothing
      if(firstInBlock != 0)
      {
        //get the text and value of the one space above the block
        var oText = colList.options[firstInBlock-1].text;
        var oValue = colList.options[firstInBlock-1].value;

        if ( descArray != (void 0) )
          var dValue = descArray[firstInBlock - 1];

        //move the block up one at a time
        for (var i = firstInBlock; i <= lastInBlock; i++)
        {
          colList.options[i-1].text = colList.options[i].text;
          colList.options[i-1].value = colList.options[i].value;
          colList.options[i-1].selected = true;

          if ( descArray != (void 0) )
            descArray[i-1] = descArray[i];
        }

         //put the info of the slot above the selection below it
        colList.options[lastInBlock].text = oText;
        colList.options[lastInBlock].value = oValue;
        colList.options[lastInBlock].selected = false;

        if ( descArray != (void 0) )
          descArray[lastInBlock] = dValue;
      }
    }
    else
    {
      // move this block down
      // if we are at the bottom, do nothing
      if(lastInBlock != colList.length-2)
      {
        //get the text and value of the one space below the block
        var oText = colList.options[lastInBlock+1].text;
        var oValue = colList.options[lastInBlock+1].value;

        if ( descArray != (void 0) )
          var dValue = descArray[lastInBlock+1];

         //move the block down one at a time
        for (var i = lastInBlock; i >= firstInBlock; i--)
        {
          colList.options[i+1].text = colList.options[i].text;
          colList.options[i+1].value = colList.options[i].value;
          colList.options[i+1].selected = true;

          if ( descArray != (void 0) )
            descArray[i+1] = descArray[i];
        }

         //put the info of the slot below the selection above it
        colList.options[firstInBlock].text = oText;
        colList.options[firstInBlock].value = oValue;
        colList.options[firstInBlock].selected = false;

        if ( descArray != (void 0) )
          descArray[firstInBlock] = dValue;
      }
    }

    processed = tempIndex - 1;
  }

  TrShuttleProxy._displayDesc( list, formName );

  //make the list for submission
  TrShuttleProxy._makeList(formName, list);
}

/*
 * _orderTopBottomList
 *
 * This function reorders the given list by shifting the selections all the way
 * in the given direction.  If no formName is supplied, the form is found when
 * this is called. The 'list' parameter should be the
 * list name(i.e.  "<shuttleName>:leading" or "<shuttleName>:trailing")
 */
TrShuttleProxy._orderTopBottomList = function(
  down,
  list,
  formName
  )
{
  //get the formname if needed
  if(formName == (void 0))
  {
    formName = TrShuttleProxy._findFormNameContaining(list);
  }

  //get the actual list
  var colList = document.forms[formName].elements[list];

  //get all the indexes of the items selected in the list
  var selItems = TrShuttleProxy._getSelectedIndexes(formName, list);

  //if no items are selected, return with alert.
  if(selItems.length == 0)
  {
    if (_shuttle_no_items_selected.length > 0)
      alert(_shuttle_no_items_selected);

    return;
  }

  var descArray = TrShuttleProxy._getDescArray(list);
  var moveDescArray = new Array();
  var selDescArray = new Array();

  var moveItemsText = new Array();
  var moveItemsValue = new Array();
  var moveItemsIndex = 0;
  if(down == 0)
  {
    //get an array of all the items we will have to displace in order
    var selItemsIndex = 0;
    var moveItemsIndex = 0;
    for(var colListIndex=0;
        colListIndex < selItems[selItems.length - 1];
        colListIndex++)
    {
      if(colListIndex != selItems[selItemsIndex])
      {
        moveItemsText[moveItemsIndex] = colList.options[colListIndex].text;
        moveItemsValue[moveItemsIndex] = colList.options[colListIndex].value;

        if (  descArray != (void 0) )
          moveDescArray[moveItemsIndex] = descArray[colListIndex];

        moveItemsIndex++
      }
      else
      {

        if ( descArray != (void 0) )
          selDescArray[selItemsIndex] = descArray[colListIndex];

        selItemsIndex++;

      }
    }

    if ( descArray != (void 0) )
      selDescArray[selItemsIndex] = descArray[colListIndex];


    //place items to move toward top of col
    for(var i = 0; i < selItems.length; i++)
    {
      colList.options[i].text = colList.options[selItems[i]].text;
      colList.options[i].value = colList.options[selItems[i]].value;
      colList.options[i].selected = true;

      if ( descArray != (void 0) )
        descArray[i] = selDescArray[i];
    }

    //place displaced items below
    for(var j = 0; j < moveItemsText.length; j++)
    {
      colList.options[i].text = moveItemsText[j];
      colList.options[i].value = moveItemsValue[j];
      colList.options[i].selected = false;

      if ( descArray != (void 0) )
        descArray[i] = moveDescArray[j];
      i++
    }



  }
  else
  {
    //get an array of all the items we will have to displace in order
    var selItemsIndex = 1;
    var moveItemsIndex = 0;

    if ( descArray != (void 0) )
      selDescArray[0] = descArray[selItems[0]];

    for(var colItemsIndex=selItems[0]+1;
        colItemsIndex <= colList.length-2;
        colItemsIndex++)
    {
      if((selItemsIndex == selItems.length) ||
         (colItemsIndex != selItems[selItemsIndex]))
      {
        moveItemsText[moveItemsIndex] = colList.options[colItemsIndex].text;
        moveItemsValue[moveItemsIndex] = colList.options[colItemsIndex].value;

        if ( descArray != (void 0) )
          moveDescArray[moveItemsIndex] = descArray[colItemsIndex];

        moveItemsIndex++;
      }
      else
      {
        if ( descArray != (void 0) )
          selDescArray[selItemsIndex] = descArray[colItemsIndex];

        selItemsIndex++;
      }
    }


    //place items to move toward bottom of col
    var j = colList.length - 2;
    for(var i = selItems.length-1; i >= 0; i--)
    {
      colList.options[j].text = colList.options[selItems[i]].text;
      colList.options[j].value = colList.options[selItems[i]].value;
      colList.options[j].selected = true;

      if ( descArray != (void 0) )
        descArray[j] = selDescArray[i];
      j--;
    }


    //place displaced items above
    for(var i = moveItemsText.length-1; i >= 0; i--)
    {
      colList.options[j].text = moveItemsText[i];
      colList.options[j].value = moveItemsValue[i];
      colList.options[j].selected = false;

      if ( descArray != (void 0) )
        descArray[j] = moveDescArray[i];
      j--
    }
  }

  TrShuttleProxy._displayDesc( list, formName );

  //make the list for submission
  TrShuttleProxy._makeList(formName, list);
}

// helper functions
TrShuttleProxy._getSelectedIndexes = function(
  formName,
  listName
  )
{
  var colList = document.forms[formName].elements[listName];
  var selItems = new Array();
  var selItemsIndex=0;
  //minus 1 for bars
  for(var colListIndex=0; colListIndex<colList.length-1; colListIndex++)
  {
    if(colList.options[colListIndex].selected)
    {
      selItems[selItemsIndex] = colListIndex;
      selItemsIndex++;
    }
  }

  return selItems;
}

TrShuttleProxy._findFormNameContaining = function(
  element
  )
{
  var formsLength = document.forms.length;

  for (var i = 0; i < formsLength; i++)
  {
    if (document.forms[i][element] != (void 0))
    {
      return _getFormName(document.forms[i]);
    }
  }
  return "";
}

TrShuttleProxy._makeList = function(
  formName,
  listName
  )
{
  var list = document.forms[formName].elements[listName];

  if ( list == null )
    return;


  var val = "";
  for(var i=0; i< list.length - 1; i++)
  {
    if(list.options[i].value.length > 0)
    {
      val = val +
            TrShuttleProxy._trimString(list.options[i].value)
            + ';';
    }
    else
    {
      val = val +
            TrShuttleProxy._trimString(list.options[i].text)
            + ';';
    }
  }
  document.forms[formName].elements[listName+':items'].value = val;
}

TrShuttleProxy._trimString = function (
  str
  )
{
  var j = str.length - 1;
  if(str.charAt(j) != ' ')
  {
    return str;
  }
  while ((str.charAt(j) == ' ') && (j > 0))
  {
    j = j - 1;
  }
  str = str.substring(0, j+1);
  return str;
}

TrShuttleProxy._getListName = function(
  shuttleName,
  leadingList
  )
{
  var theListName = (leadingList) ? shuttleName + ":leading" :
                                  shuttleName + ":trailing";
  return theListName;
}


/**
* Reset items to their original values
*
*/
TrShuttleProxy._resetItems = function(
  shuttleName,
  formName)
{
  // get list names
  leadingListName = TrShuttleProxy._getListName( shuttleName, true);
  trailingListName = TrShuttleProxy._getListName( shuttleName, false);

  // get current lists
  var leadingList  = document.forms[formName].elements[leadingListName];
  // Defensive:  reset calls are left attached to the page even after
  // the element has been removed.  So if the list is not found, just bail
  if (!leadingList)
    return;

  var trailingList = document.forms[formName].elements[trailingListName];

  // get original lists
  var origLists = TrShuttleProxy._getOriginalLists(shuttleName, formName);
  var origLeadingList  = origLists.leading;
  var origTrailingList = origLists.trailing;

  // get original description arrays
  var origLeadingDescArray =  TrShuttleProxy._getDescArray(leadingListName);
  var origTrailingDescArray = TrShuttleProxy._getDescArray(trailingListName);

  // reset values of lists
  TrShuttleProxy._resetToOriginalList( origLeadingList, origLeadingDescArray, leadingList );
  TrShuttleProxy._resetToOriginalList( origTrailingList, origTrailingDescArray, trailingList );

  //make the new lists for submitting.
  TrShuttleProxy._makeList(formName, leadingListName);
  TrShuttleProxy._makeList(formName, trailingListName);

  // return that no reload necessary
  return false;
}



/*
 * _getOriginalLists
 *
 * This function gets a copy of the original lists list
 */
TrShuttleProxy._getOriginalLists = function
(
  shuttleName,
  formName
)
{

  var originalLists = window['_' + formName + '_' + shuttleName + '_orig'];
  return originalLists;

}

/**
 * Given the original list info,
 * reset the list and description info
 *
 */
TrShuttleProxy._resetToOriginalList = function
(
  origList,
  descArray,
  list
)
{

  // If the original list or new list are null, return
  if ( origList == (void 0) || list == (void 0) )
    return;

  // reset selectedIndex
  list.selectedIndex = origList.selectedIndex;

  var i = 0;

  for( ;i < origList.options.length;i++)
  {
    var oText            = origList.options[i].text;
    var oValue           = origList.options[i].value;
    var oDefaultSelected = origList.options[i].defaultSelected;
    var oSelected        = origList.options[i].selected;

    // =-= gc Replacing values for options that already exist
    // doesn't work properly on Mozilla 1.3. If you
    // uncomment the following it breaks on mozilla, but if you then
    // add "alert('hi');" at the end of this method, it does work!
    /*
    if(list.options[i] != (void 0 ))
    {
      list.options[i].text = oText;
      list.options[i].value = oValue;
      list.options[i].defaultSelected = oDefaultSelected;
      list.options[i].selected = oSelected;
    }
    else
    */
    {
      // make new option
      list.options[i] = new Option(oText, oValue,
                                   oDefaultSelected, oSelected);

      // Netscape 4 bug, selection using constructor not working,
      // Remove next 2 lines when we desupport Netscape 4.
      list.options[i].defaultSelected = oDefaultSelected;
      list.options[i].selected = oSelected;
    }

    // reset description
    if ( descArray != (void 0 ))
      descArray[i] = origList.descriptions[i];

  }



  var max = list.options.length - 1;

  // nulling out extra options from high end of array
  // because doing otherwise not working.
  while ( max >= i )
  {
    if ( descArray != (void 0 ))
      descArray[max] = null;

    list.options[max] = null;
    max--;
  }


 }


 /**
  * _copyLists
  *
  * Copy the lists and descriptions info to an object
  *
  *
  */
 TrShuttleProxy._copyLists = function( shuttleName, formName )
 {
   var origLists = new Object();
   origLists.leading = TrShuttleProxy._copyList( TrShuttleProxy._getListName( shuttleName, true), formName);
   origLists.trailing = TrShuttleProxy._copyList( TrShuttleProxy._getListName( shuttleName, false), formName);


   return origLists;
 }


 /**
  * _copyList
  *
  * copy the values in a single list
  */
 TrShuttleProxy._copyList = function( listName, formName )
 {
   if ( formName == (void 0 ) || listName == (void 0))
     return;

   var origList = document.forms[formName].elements[listName];

   if ( origList == null)
     return;

   var origDescs = TrShuttleProxy._getDescArray(listName);

   var copyList = new Object();


   copyList.selectedIndex = origList.selectedIndex;
   copyList.options = new Array();
   copyList.descriptions = new Array();

   for ( var i = 0; i < origList.options.length; i++ )
   {
     copyList.options[i] = new Option( origList.options[i].text,
                                       origList.options[i].value,
                                       origList.options[i].defaultSelected,
                                       origList.options[i].selected);

     // Netscape 4 bug, selection using constructor not working,
     // Remove next 2 lines when we desupport Netscape 4.
     copyList.options[i].defaultSelected = origList.options[i].defaultSelected;
     copyList.options[i].selected = origList.options[i].selected;

     if (origDescs != null )
       copyList.descriptions[i] = origDescs[i];
   }


   return copyList;
 }

/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 * 
 *  http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */

/**
 * Simple function for opening a popup
 * @param contentId(String) id of the element to pop
 * @param triggerId(String) optional id of the element that launched the popup
 * @param event(Event) the javascript event object (used to position relative popups)
 * @param triggerType(String) 'click'(default) | 'hover'
 * @param position(String) 'relative'(default) | 'centered'
 * @param modal(boolean)
 * @param width(int) 
 * @param height(int)
 * @param xOffset(int)
 * @param yOffset(int)
 **/
TrPanelPopup.showPopup = function(
  contentId, 
  triggerId, 
  event, 
  triggerType,
  position, 
  modal, 
  width, 
  height, 
  xOffset, 
  yOffset)
{
  if (contentId == null)
    return;
  
  // Get/Initialize a map of visible popups
  var visiblePopups = TrPanelPopup._VISIBLE_POPUPS;
  if (!visiblePopups)
    visiblePopups = TrPanelPopup._VISIBLE_POPUPS = new Object();
  
  // Check if the popup is already visible
  if (visiblePopups[contentId])
    // Popup is already visible
    return;
    
  // Create new popup object and add it to the map of visible popups
  if (triggerType == "hover")
    visiblePopups[contentId] = new TrHoverPopup();
  else
    visiblePopups[contentId] = new TrClickPopup();

  var popup = visiblePopups[contentId];

  var content = document.getElementById(contentId);
  if (!content)
     return;

  popup.setContent(content);
  popup.setTrigger(document.getElementById(triggerId));
  popup.setModal(modal);
  popup.setCentered(position == 'centered');
  popup.setSize(width, height);
  popup.setRelativeOffsetX(xOffset);
  popup.setRelativeOffsetY(yOffset);
  
  popup.showPopup(event);
}

/**
 * Public function for hiding the current popup.
 */  
TrPanelPopup.hidePopup = function(event)
{
  event = window.event || event;
  var visiblePopups = TrPanelPopup._VISIBLE_POPUPS;
  if (!visiblePopups)
    return;

  //loop through element stack and find out which popup the event occured in.
  var currElement = event.target || event.srcElement;
  while (currElement)
  {
    var id = currElement.id;
    if (id)
    {
      var currPopup = visiblePopups[id];
      if (currPopup)
      {
        // We found the popup, so hide it.
        currPopup.hide(event);
        break;
      }
    }
    currElement = currElement.parentNode;
  }
}

/**
 * Class to handle a popup element on a page.
 */
function TrPanelPopup()
{
  //define object properties
  this._content = false;
  this._trigger = false;
  this._centered = false;
  this._modal = false;
  this._visible = false;
}

TrPanelPopup.prototype.getContent = function()
{
  return this._content;
}

TrPanelPopup.prototype.setContent = function(content)
{ 
  this._content = content;
  
  //Initialize the styles for the content
  if (this._content)
  {
    this._content.style.cssText  = "position: absolute; z-index: 5001; top: 0px; left: 0px; visibility:hidden; padding: 0px;";  
  }
}

/**
 * Get the element being used as the trigger
 **/
TrPanelPopup.prototype.getTrigger = function()
{
  return this._trigger;
}

/**
 * Sets the element to be used as the trigger to show the popup.  We
 * use this to ensure further events on the trigger don't cause a re-popup.
 * @param trigger(Element) The element that will trigger the popup.
 **/
TrPanelPopup.prototype.setTrigger = function(trigger)
{
  this._trigger = trigger;
}

/**
 * Sets the popup to be centered on screen when visible
 * @param centered(boolean) true if popup should be centered
 **/
TrPanelPopup.prototype.setCentered = function(centered)
{
  this._centered = centered;
}

/**
 * Returns true if the popup is set to modal.
 **/
TrPanelPopup.prototype.isModal = function()
{
  return this._modal;
}

/**
 * Sets the popup to be modal when visible
 */
TrPanelPopup.prototype.setModal = function(modal)
{
  this._modal = modal;
}

/**
 * Sets X offset to apply if popup is positioned relative to mouse x.
 * @param x(int) The x offset value.
 **/
TrPanelPopup.prototype.setRelativeOffsetX = function(x)
{
  this._relativeOffsetX = parseInt(x);
}

/**
 * Gets X offset to apply if popup is positioned relative to mouse x.
 * @return (int) The x offset value, or zero if unset.
 **/
TrPanelPopup.prototype.getRelativeOffsetX = function()
{
  return (this._relativeOffsetX) ? this._relativeOffsetX: 0;
}

/**
 * Sets Y offset to apply if popup is positioned relative to mouse y.
 * @param y(int) The y offset value.
 **/
TrPanelPopup.prototype.setRelativeOffsetY = function(y)
{
  this._relativeOffsetY = parseInt(y);
}

/**
 * Gets Y offset to apply if popup is positioned relative to mouse y.
 * @return (int) The y offset value, or zero if unset.
 **/
TrPanelPopup.prototype.getRelativeOffsetY = function()
{
  return (this._relativeOffsetY) ? this._relativeOffsetY: 0;
}


/**
 * Returns true if the popup is currently visible.
 **/
TrPanelPopup.prototype.isVisible = function()
{
  return this._visible;
}

/**
 * Holds the return value of the dialog.  Check this property after the 
 * popup has closed.
 **/
TrPanelPopup.prototype.returnValue = undefined;

/**
 * Attach a callback function that will be invoked when the popup
 * has been closed.  The callbackProps and returnValue properties will be
 * passed as parameters (e.g. function myCallback(props, value);).
 **/
TrPanelPopup.prototype.callback = undefined;

/**
 * Attach properties to the popup that will be passed to the callback function
 * (e.g. a component target to populate with the returnValue).
 **/
TrPanelPopup.prototype.callbackProps = undefined;

/**
 * Make the popup visible
 **/
TrPanelPopup.prototype.show = function(event)
{
  //we can't show content that isn't there
  if (!this.getContent())
    return;
 
  //don't pop during ppr - safety check
  if (_pprBlocking)
    return;

  //already visible
  if (this.isVisible())
    return;

  this._calcPosition(event);
  
  if (this.isModal())
    TrPanelPopup._showMask();
  
  TrPanelPopup._showIeIframe();

  this.getContent().style.visibility = "visible"; 
  
  this._visible = true;
}

/**
 * Hide the popup if visible.  Hiding the popup causes the callback
 * handler to be invoked (if configured).
 **/
TrPanelPopup.prototype.hide = function(event)
{
  //we can't hide content that isn't there
  if (!this.getContent())
    return;

  if (this.isModal())
    TrPanelPopup._hideMask();
  
  TrPanelPopup._hideIeIframe();
  
  this.getContent().style.visibility = "hidden";
  //move popup back to top left so it won't affect scroll size if window resized
  this.getContent().style.left = "0px";
  this.getContent().style.top = "0px";
  
  //call the callback function if attached
  if (this.callback)
  {
    try
    {
      this.callback(this.callbackProps, this.returnValue);
    }
    catch(ex)
    {
      alert("Error calling TrPanelPopup callback function:\n" + ex);
    }
  }
  
  this._visible = false;
  
  // Remove the popup from the list of visible popups
  var popups = TrPanelPopup._VISIBLE_POPUPS;
  if (popups)
    delete popups[this.getContent().id];
}

/**
 * Size the popup to a specific width and height
 */
TrPanelPopup.prototype.setSize = function(width, height)
{
  if (width)
  {
    var i = parseInt(width);
    if (i > 0)
      this.getContent().style.width = i + "px";
  }
  if (height)
  {
    var i = parseInt(height);
    if (i > 0)
      this.getContent().style.height = i + "px";
  }
}

// The modal mask - shared by all instances
TrPanelPopup._mask = undefined;

/**
 * Show the popup mask that blocks clicks in modal mode.  Initialize it
 * if not already.
 **/
TrPanelPopup._showMask = function()
{
  //initialise mask only once
  if (!TrPanelPopup._mask)
  {
    //create mask for modal popups
    TrPanelPopup._mask = document.createElement('div');
    TrPanelPopup._mask.name = "TrPanelPopup._BlockingModalDiv";

    //optional: id of blocked area
    TrPanelPopup._mask.id = "af_dialog_blocked-area";

    //set style class for blocked area
    var page = TrPage.getInstance();
    TrPanelPopup._mask.className = page.getStyleClass("af|dialog::blocked-area");

    var cssText = "display:none;position: absolute; z-index: 5000;top: 0px;left: 0px;cursor: not-allowed;";
    if (_agent.isIE && _agent.version == 7)
      //workaround for bug in IE7 : see http://blog.thinkature.com/index.php/2006/12/29/odd-mouse-handling-with-transparent-objects-under-internet-explorer-7/
      cssText = cssText + "background-color: white; filter: alpha(opacity=0);";
    else
      cssText = cssText + "background-color: transparent";
    TrPanelPopup._mask.style.cssText = cssText;
    TrPanelPopup._mask.innerHTML = "&nbsp;";

    //add mask to body
    document.body.appendChild(TrPanelPopup._mask);
  }

  TrPanelPopup._registerMaskEvents();

  //set initial mask size
  TrPanelPopup._setMaskSize();

  TrPanelPopup._mask.style.display = "block";
  
}

TrPanelPopup._registerMaskEvents = function()
{
  //consume all events
  _addEvent(TrPanelPopup._mask, "click", TrPanelPopup._consumeMaskEvent);

  //handle window resize events
  _addEvent(window, "resize", TrPanelPopup._setMaskSize);

  //handle window scroll events
  _addEvent(window, "scroll", TrPanelPopup._setMaskSize);
}

/**
 * Hide the popup mask that blocks clicks in modal mode.
 **/
TrPanelPopup._hideMask = function()
{
  _removeEvent(TrPanelPopup._mask, "click", TrPanelPopup._consumeMaskEvent);
  _removeEvent(window, "resize", TrPanelPopup._setMaskSize);
  _removeEvent(window, "scroll", TrPanelPopup._setMaskSize);
  TrPanelPopup._mask.style.display = "none";
}

/**
 * Check to see if a point lies inside the bounds of an element
 */
TrPanelPopup.prototype._hitTest = function(element, eventLoc)
{
  var b = TrUIUtils._getElementBounds(element);
  
  return b.x <= eventLoc.pageX && (b.x + b.w) >= eventLoc.pageX &&
    b.y <= eventLoc.pageY && (b.y + b.h) >= eventLoc.pageY;
}

/**
 * Reposition an element to ensure that it fits on the screen
 */
TrPanelPopup.prototype._fitOnScreen = function(element, windowSize)
{
  var vis = TrUIUtils._getStyle(element, 'visibility');
  element.style.visibility = 'hidden';
  var b = TrUIUtils._getElementBounds(element);
  var parentLoc = TrUIUtils._getElementLocation(element.offsetParent);
  var posType = TrUIUtils._getStyle(element.offsetParent, 'position');
  var parentOffset;
  if (posType == 'relative' || posType == 'absolute')
  {
    parentOffset = { left: parentLoc.x, top: parentLoc.y };
  }
  else
  {
    parentOffset = { left: 0, top: 0 };
  }
  
  // calculate the client location of the popup (not the page location)
  var clientLoc = { 
    x: b.x - (document.body.scrollLeft || document.documentElement.scrollLeft),
    y: b.y - (document.body.scrollTop || document.documentElement.scrollTop)
  };
  
  // is the popup off the page to the left?
  if (b.x < 0)
  {
    element.style.left = (0 - parentOffset.left) + 'px';
  }
  // is it off the page to the right?
  else if (clientLoc.x + b.w > windowSize.w)
  {
    element.style.left = (element.offsetLeft - (clientLoc.x + b.w - windowSize.w)) + 'px';
  }

  // is the popup off the top of the page?
  if (b.y < 0)
  {
    element.style.top = (0 - parentOffset.top) + 'px';
  }
  // is it past the bottom the page?
  else if (clientLoc.y + b.h > windowSize.h)
  {
    element.style.top = (element.offsetTop - (clientLoc.y + b.h - windowSize.h)) + 'px';
  }
  element.style.visibility = vis;
}

/**
 * Get the page X and Y and the client X and Y of an event
 */
TrPanelPopup.prototype._getEventPosition = function(event)
{
  // all browsers
  var pos = { 
    clientX: event.clientX,
    clientY: event.clientY,
    pageX: event.pageX,
    pageY: event.pageY
  };

  if (pos.pageX == null)
  {
    pos.pageX = event.clientX
      + (document.body.scrollLeft || document.documentElement.scrollLeft);
    pos.pageY = event.clientY
      + (document.body.scrollTop || document.documentElement.scrollTop);
  }

  return pos;
}

/**
 * Function to center an element on the screen
 */
TrPanelPopup.prototype._centerOnScreen = function(element, windowSize)
{
  element.style.position = 'absolute';
  var vis = TrUIUtils._getStyle(element, 'visibility');
  element.style.visibility = 'hidden'; // prevent flickering
  var parentLoc = TrUIUtils._getElementLocation(element.offsetParent);
  var pageLoc = TrUIUtils._getElementBounds(element);
  var posType = TrUIUtils._getStyle(element.offsetParent, 'position');
  var parentOffset;
  if (posType == 'relative' || posType == 'absolute')
  {
    parentOffset = { left: parentLoc.x, top: parentLoc.y };
  }
  else
  {
    parentOffset = { left: 0, top: 0 };
  }
    
  // calculate the client location of the popup (not the page location)
  var clientLoc = { 
    x: pageLoc.x - (document.body.scrollLeft || document.documentElement.scrollLeft),
    y: pageLoc.y - (document.body.scrollTop || document.documentElement.scrollTop)
  };

  element.style.left = Math.max(0,
    (windowSize.w / 2 - element.clientWidth / 2)
    - parentOffset.left
    + (pageLoc.x - clientLoc.x)) + 'px';
  
  element.style.top = Math.max(0,
    (windowSize.h / 2 - element.clientHeight / 2)
    - parentOffset.top
    + (pageLoc.y - clientLoc.y)) + 'px';
  
  element.style.visibility = vis;
}

/**
 * Get the element to add to the dialog to, to ensure dialog
 * positioning
 */
TrPanelPopup.prototype._getOffsetParent = function()
{
  for (var elem = this.getContent(); elem != null;
    elem = elem.parentNode)
  {
    if (elem.tagName && 'form' == elem.tagName.toLowerCase())
    {
      return elem;
    }
  }
  return document.body;
}

/**
 * Position the popup ensuring it doesn't go off-page, and if centered, then 
 * center in the middle of the current window.
 */
TrPanelPopup.prototype._calcPosition = function(event)
{
  var popup = this.getContent();
  event = window.event || event;
  
  var parent = this._getOffsetParent(); 
  // get the window size before the popup may alter it
  var wSize = TrUIUtils._getWindowClientSize();
  
  if (!popup.origParent)
  {
    popup.origParent = popup.parentNode;
  }  
  parent.appendChild(popup);

  if (!this._centered)
  {
    var eventP = this._getEventPosition(event);
    var parLoc = TrUIUtils._getElementLocation(popup.offsetParent);
    var posType = TrUIUtils._getStyle(popup.offsetParent, 'position');
    var parentOffset;
    if (posType == 'relative' || posType == 'absolute')
    {
      parentOffset = { left: parLoc.x, top: parLoc.y };
    }
    else
    {
      parentOffset = { left: 0, top: 0 };
    }

    // set the location to the location of the event on the page
    // adjusted to the parent location on the page
    // adjusted by the relative offset of the popup
    popup.style.left = (eventP.pageX - parentOffset.left + this.getRelativeOffsetX() -
      this._getSideOffset(popup, "Left")) + 'px';
    
    popup.style.top = (eventP.pageY - parentOffset.top + this.getRelativeOffsetY() -
      this._getSideOffset(popup, "Top")) + 'px';
  }
  
  if (this._centered)
  {
    this._centerOnScreen(popup, wSize);
  }
  else
  {
    this._fitOnScreen(popup, wSize);
  }
  
  if (!this.isModal())
  {
    var b = TrUIUtils._getElementBounds(popup);
    TrPanelPopup._resizeIeIframe(b.x, b.y, b.w, b.h);
  }
}

TrPanelPopup.prototype._getSideOffset = function(elem, side)
{
  var arr = [ "border", "padding", "margin" ];
  var val = 0;
  for (var i = 0; i < arr.length; ++i)
  {
    var o = TrUIUtils._getStyle(elem, arr[i] + side);
    o = parseInt(o);
    if (!isNaN(o))
    {
      val += o;
    }
  }
  return val;
}

/**
 * Simple event handler that consumes any clicks when modal popup is shown
 */
TrPanelPopup._consumeMaskEvent = function(event)
{
  return false;
}

/**
 * Sizes/resizes the modal mask if the window size changes
 */
TrPanelPopup._setMaskSize = function()
{
  //only bother if mask is inited
  if (!TrPanelPopup._mask)
    return;

  var wSize = TrUIUtils._getWindowClientSize();
  var scrollWidth = document.documentElement.scrollWidth || document.body.scrollWidth;
  var scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
    
  var w = Math.max(wSize.w, scrollWidth);
  var h = Math.max(wSize.h, scrollHeight);
  
  TrPanelPopup._mask.style.width = w + "px";
  TrPanelPopup._mask.style.height = h + "px";
  
  TrPanelPopup._resizeIeIframe(0, 0, w, h);
}

/**
 * FUNCTIONS BELOW IMPLEMENT CSS/IFRAME WORKAROUND FOR THE INFAMOUS IE 6.x SELECT ZINDEX BUG
 * More info here: http://dotnetjunkies.com/WebLog/jking/archive/2003/07/21/488.aspx
 **/
TrPanelPopup._showIeIframe = function()
{
  if (_agent.isIE && _agent.version < 7)
  {
    TrPanelPopup._initIeIframe();
    TrPanelPopup._maskIframe.style.display = "block";      
  }
}

TrPanelPopup._hideIeIframe = function()
{
  if (_agent.isIE && _agent.version < 7)
  {
    TrPanelPopup._initIeIframe();
    TrPanelPopup._maskIframe.style.display = "none";      
  }
}

TrPanelPopup._resizeIeIframe = function(left, top, width, height)
{
  if (_agent.isIE && _agent.version < 7)
  {
    TrPanelPopup._initIeIframe();
    TrPanelPopup._maskIframe.style.left = left;
    TrPanelPopup._maskIframe.style.top = top;
    TrPanelPopup._maskIframe.style.width = width;
    TrPanelPopup._maskIframe.style.height = height;
  }
}

TrPanelPopup._initIeIframe = function()
{
  if (!TrPanelPopup._maskIframe)
  {
    //create single reusable iframe if not already inited
    TrPanelPopup._maskIframe = document.createElement('iframe');
    TrPanelPopup._maskIframe.name = "TrPanelPopup._ieOnlyZIndexIframe";
    TrPanelPopup._maskIframe.style.cssText = "display: none; left: 0px; position: absolute; top: 0px; z-index: 4999;";
    TrPanelPopup._maskIframe.style.filter = "progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)";
    // avoid SSL warnings in IE
    if (_agent.isIE)
    {
      TrPanelPopup._maskIframe.src = "javascript:false;";
    }
    document.body.appendChild(TrPanelPopup._maskIframe);
  }
}








function TrHoverPopup()
{
  TrPanelPopup.call(this);

  // Setup callback function for hiding the popup
  this._hoverCallbackFunction = TrUIUtils.createCallback(this, this.hidePopup);
}

// TrHoverPopup inherits from TrPanelPopup
TrHoverPopup.prototype = new TrPanelPopup();

TrHoverPopup.prototype.showPopup = function(event)
{
  // Setup event listener for mouse leaving trigger or content elements
  //_addEvent(this.getTrigger(), "mouseout", this._hoverCallbackFunction);
  //_addEvent(this.getContent(), "mouseout", this._hoverCallbackFunction);
  
  // mouse out on the elements didn't always get called, so use mouse move
  _addEvent(document.body, "mousemove", this._hoverCallbackFunction);
  
  this.show(event);
}

TrHoverPopup.prototype.hidePopup = function(event)
{
  event = window.event || event;
  
  var popup = this.getContent();
  var trigger = this.getTrigger();
  
  var eventPos = this._getEventPosition(event);
  
  // see if event is in the popup or the trigger bounds
  if ((this._hitTest(popup, eventPos) ||
    this._hitTest(trigger, eventPos)))
  {
    return;
  }
  
  // Cancel event listeners
  // mouse out on the elements didn't always get called, so use mouse move
  //_removeEvent(this.getTrigger(), "mouseout", this._hoverCallbackFunction);
  //_removeEvent(this.getContent(), "mouseout", this._hoverCallbackFunction);
  _removeEvent(document.body, "mousemove", this._hoverCallbackFunction);

  this.hide(event);
  
  if (popup.origParent)
  {
    popup.origParent.appendChild(popup);
  }
}

TrHoverPopup.prototype.isModal = function()
{
  // Prevent modal for hover popups
  return false;
}




function TrClickPopup()
{
  TrPanelPopup.call(this);

  // Setup callback function for hiding the popup
  this._clickCallbackFunction = TrUIUtils.createCallback(this, this.hidePopup);
}

// TrHoverPopup inherits from TrPanelPopup
TrClickPopup.prototype = new TrPanelPopup();

TrClickPopup.prototype.showPopup = function(event)
{
  if (!this.isModal())
    // Setup event listener for clicking off the popup
    _addEvent(document, "click", this._clickCallbackFunction);
    
  this.show(event);
}

TrClickPopup.prototype.hidePopup = function(event)
{
  //loop through element stack where event occurred
  event = window.event || event;
  var currElement = event.target || event.srcElement;
  while (currElement)
  {
    //if clicked on trigger or popup  
    if (currElement == this.getContent() || 
        currElement == this.getTrigger())
    {
      break;
    }
    currElement = currElement.parentNode;
  }
  
  if (!currElement)
  {
    // Cancel event listeners
    _removeEvent(document, "click", this._clickCallbackFunction);

    //if click was on something other than the popupContainer
    this.hide(event);
    
    if (this.getContent().origParent)
    {
      this.getContent().origParent.appendChild(this.getContent());
    }
  }
}



/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 * 
 *  http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */
function TrPopupDialog()
{
  var page = TrPage.getInstance();

  var div = document.createElement("div");
  div.style.cssText = "visibility:hidden; position: absolute;";
  div.className = page.getStyleClass("af|dialog::container");
  
  //setup the title bar
  var titlebar = document.createElement("div");
  titlebar.className = page.getStyleClass("af|dialog::title-bar");
  div.appendChild(titlebar);

  //setup the title bar
  var titleText = document.createElement("div");
  titleText.style.cssText = "float:left;";
  titleText.className = page.getStyleClass("af|dialog::title-text");
  titlebar.appendChild(titleText);

  //setup the title bar
  var closerDiv = document.createElement("div");
  closerDiv.style.cssText = "float:right;";
  closerDiv.className = page.getStyleClass("af|dialog::close-icon");
  _addEvent(closerDiv, "click", TrPopupDialog._returnFromDialog);
  titlebar.appendChild(closerDiv);
  
  //setup the title bar
  var toggleDiv = document.createElement("div");
  toggleDiv.style.cssText = "float:right;";
  toggleDiv.className = page.getStyleClass("af|dialog::undisclose-icon");
  _addEvent(toggleDiv, "click", TrPopupDialog._toggleDialog);
  titlebar.appendChild(toggleDiv);

  //Hold the div so we can change the styleClass
  this._toggleDiv = toggleDiv;

  //setup the title bar
  var sepDiv = document.createElement("div");
  sepDiv.style.cssText = "clear:both;";
  titlebar.appendChild(sepDiv);

  //setup the content iframe
  var iframe = document.createElement("iframe");
  iframe.name = "_blank";
  iframe.frameBorder = "0";
  iframe.className = page.getStyleClass("af|dialog::content");
  div.appendChild(iframe);
  
  // Hold the iframe so we can set the 'src' as needed, and also the height
  this._iframe = iframe;

  // Hold on to the outer div so we can set the width
  this._outerDiv = div;

  // Hold on to the text div so we can update the title
  this._titleTextDiv = titleText;
    
  document.body.appendChild(div);
  
  TrPanelPopup.call(this);
  this.setModal(true);
  this.setCentered(true);
  this.setContent(div);

  // flag to indicate if dialog size should be locked
  this._fixedSize = false;
  
  // iframe height which is set by click on toggle button
  this._toggleHeight = "0px";

}

// TrPopupDialog inherits from TrPanelPopup
TrPopupDialog.prototype = new TrPanelPopup();

/**
 * Set the title bar text
 **/
TrPopupDialog.prototype.setTitle = function(title)
{
  if (title)
  {
    this._titleTextDiv.innerHTML = title;
  }
  else
  {
    this._titleTextDiv.innerHTML = "";
  }
}

TrPopupDialog.prototype.setDestination = function(url)
{
  this._iframe.src = url;
}

TrPopupDialog.prototype.setSize = function(width, height)
{
    this._resizeIFrame(width, height);

    if(width == null)
    {
      this._variableWidth = true;
    }
    else
    {
      this._variableWidth = false;
      this._fixedSize = true;
    }

    if(height == null)
    {
      this._variableHeight = true;
    }
    else
    {
      this._variableHeight = false;
      this._fixedSize = true;
    }
}

TrPopupDialog.getInstance = function()
{
  return TrPopupDialog.DIALOG;
}

/**
 * Clean up the dialog
 */
TrPopupDialog.prototype._destroy = function()
{
  // Remove the DIV from the body, and delete any
  // references to DOM in case someone is bad and holds onto
  // this JS object
  var div = this._outerDiv;
  if (div)
  {
    delete this._outerDiv;
    div.parentNode.removeChild(div);
  }
  
  if (this._iframe)
    delete this._iframe;

  if (this._titleTextDiv)
    delete this._titleTextDiv;
}


/**
 * Resize the content area of the dialog
 **/
TrPopupDialog.prototype._resizeIFrame = function(width, height)
{
    if(height != null)
    {
      // Set the height of the iframe
      this._iframe.height = height + "px";
    }

    if(width != null)
    {
      // Set the width of the iframe to 100% so it is sized by it's parent outerDiv
      this._iframe.width = "100%";
      // But set the width of the outerDiv, so the title bar is also the right size
      this._outerDiv.style.width = width + "px";
    }
    this._calcPosition(false);
}

/**
 * Called from dialog page (usually body onload) to set the dialog title to
 * that of the current page, and handle any resizing that may be required.
 **/
TrPopupDialog._initDialogPage = function()
{
  var dialog;

  try
  {
    dialog = parent.TrPopupDialog.DIALOG;
  }
  catch(err)
  {
  }

  if (!dialog)
    return;

  // Update the dialog title
  dialog.setTitle(document.title);
  
  // Exit if the dialog is already visible
  if (dialog.isVisible())
    return;
    
  // Resize the dialog to the page content
  if (!dialog._fixedSize)
  {
    if (_agent.isIE)
    {
      dialog._resizeIFrame(
        dialog._iframe.Document.body.scrollWidth+40, 
        dialog._iframe.Document.body.scrollHeight+40);
    }
    else
    {
      dialog._resizeIFrame(
        dialog._iframe.contentDocument.body.offsetWidth+40, 
        dialog._iframe.contentDocument.body.offsetHeight+40);
    }
  }
  else if(dialog._variableWidth || dialog._variableHeight)
  {
    if(dialog._variableWidth)
    {
      if (_agent.isIE)
      {
        dialog._resizeIFrame(dialog._iframe.Document.body.scrollWidth+40, null);
      }
      else
      {
        dialog._resizeIFrame(dialog._iframe.contentDocument.body.offsetWidth+40, null);
      }
    }

    if(dialog._variableHeight)
    {
      if (_agent.isIE)
      {
        dialog._resizeIFrame(null, dialog._iframe.Document.body.scrollHeight+40);
      }
      else
      {
        dialog._resizeIFrame(null, dialog._iframe.contentDocument.body.offsetHeight+40);
      }
    }
  }
  dialog.show();
}

/*
 * This function handles the closure of the dialog.  Generally, it is
 * called via PPR.
 */
TrPopupDialog._returnFromDialog = function()
{
  var dialog = TrPopupDialog.DIALOG;
  if (dialog)
  {
    dialog.hide();
    // Set a timeout to clean up the dialog later - not now, because
    // otherwise it'll kill our current submit
    window.setTimeout(TrUIUtils.createCallback(
       dialog, TrPopupDialog.prototype._destroy), 100);
    TrPopupDialog.DIALOG = undefined;
  }
  else
  {
    alert("returnFromDialog(): Error - Current popup is not a dialog");
  }
}
 
/*
 * This function handles the click on toggle button. 
 * The first click closes the iframe and the second dicloses. 
 */
TrPopupDialog._toggleDialog = function()
{
  var dialog = TrPopupDialog.DIALOG;
  if (dialog)
  {
    var page = TrPage.getInstance();
    var tmp = dialog._iframe.height;
    dialog._iframe.height = dialog._toggleHeight;
    dialog._toggleHeight = tmp;

    if (dialog._iframe.height === "0px")
    {
      dialog._toggleDiv.className = page.getStyleClass("af|dialog::disclose-icon");
    }
    else
    {
      dialog._toggleDiv.className = page.getStyleClass("af|dialog::undisclose-icon");
    }
  }
  else
  {
    alert("Error - Current popup is not a dialog");
  }
}

/*
 * Callback function, invoked on close of dialog.  If necessary, this
 * function will make a submit to cause the return event to fire.
 * TODO - Move this function to another library - TrPage perhaps?
 */
TrPopupDialog._returnFromDialogAndSubmit = function(callbackProps, value)
{
  if (callbackProps)
  {
    var formName = callbackProps['formName'];
    var postbackId = callbackProps['postback'];

    _submitPartialChange(formName, 0, {rtrn:postbackId});
  }
}

TrPopupDialog._launchDialog = function(
  srcURL,
  dialogProps,
  callbackFunction,
  callbackProps)
{
  var dialog = TrPopupDialog.DIALOG;
  if (!dialog)
  {
    dialog = TrPopupDialog.DIALOG = new TrPopupDialog();
  }

  // Register callback on close of dialog
  dialog.callback = callbackFunction;
  dialog.callbackProps = callbackProps;

    // Dialog will auto-size to fit content unless specified
    if (dialogProps && dialogProps['width'] && dialogProps['height'])
    {
      dialog.setSize(dialogProps['width'], dialogProps['height']);
    }
    else if (dialogProps && dialogProps['width'])
    {
      dialog.setSize(dialogProps['width'], null);
    }
    else if (dialogProps && dialogProps['height'])
    {
      dialog.setSize(null, dialogProps['height']);
    }

  // Dialog will be opened by _initDialogPage() once dialog page has loaded
  // to prevent lots of up/down sizing when auto-sized.
  dialog.setDestination(srcURL);
}

/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 * 
 *  http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */
function TrPage()
{
  this._loadedLibraries = TrPage._collectLoadedLibraries();
  this._requestQueue = new TrRequestQueue(window);
}


/**
 * Get the shared instance of the page object.
 */
TrPage.getInstance = function()
{
  if (TrPage._INSTANCE == null)
    TrPage._INSTANCE = new TrPage();

  return TrPage._INSTANCE;
}

/**
 * Return the shared request queue for the page.
 */
TrPage.prototype.getRequestQueue = function()
{
  return this._requestQueue;
}

/**
 * Post the form for partial postback.  Supports both standard AJAX
 * posts and, for multipart/form posts, IFRAME-based transmission.
 * @param actionForm{FormElement} the HTML form to post
 * @param params{Object} additional parameters to send
 * @param headerParams{Object} HTTP headers to include (ignored if 
 *   the request must be a multipart/form post)
 */
TrPage.prototype.sendPartialFormPost = function(
  actionForm,
  params,
  headerParams)
{
  this.getRequestQueue().sendFormPost(
    this, this._requestStatusChanged,
    actionForm, params, headerParams);
}

TrPage.prototype._requestStatusChanged = function(requestEvent)
{
  if (requestEvent.getStatus() == TrXMLRequestEvent.STATUS_COMPLETE)
  {
    var statusCode = requestEvent.getResponseStatusCode();
    
    // The server might not return successfully, for example if an
    // exception is thrown.  When that happens, a non-200 (OK) status
    // code is returned as part of the HTTP prototcol.
    if (statusCode == 200)
    {
      _pprStopBlocking(window);

      if (requestEvent.isPprResponse())
      {
        var responseDocument = requestEvent.getResponseXML();

        // Though not yet supported, Nokia browser sometimes fails to get
        // XML content. (Currently being investigated.) When that happens,
        // the function should simply return without calling
        // _handlePprResponse rather than crushing the function with null
        // pointer reference.
        // This is a temporary workaround and should be revisited when
        // Nokia browser is officially supported.
        if (responseDocument != null)
        {
          this._handlePprResponse(responseDocument.documentElement);
        }
      }
      else
      {
        // Should log some warning that we got an invalid response
      }
    }
    else if (statusCode >= 400)
    {
      // The RequestQueue logs these for us, so
      // we don't need to take action here.  IMO, that's probably
      // wrong - we should do the handling here
      _pprStopBlocking(window);
    }

  }
}

TrPage.prototype._handlePprResponse = function(documentElement)
{
  var rootNodeName = TrPage._getNodeName(documentElement);
  
  if (rootNodeName == "content")
  {
    // Update the form action
    this._handlePprResponseAction(documentElement);

    var childNodes = documentElement.childNodes;
    var length = childNodes.length;
    
    for (var i = 0; i < length; i++)
    {
      var childNode = childNodes[i];
      var childNodeName = TrPage._getNodeName(childNode);
      
      if (childNodeName == "fragment")
      {     
        this._handlePprResponseFragment(childNode);  
      }
      else if (childNodeName == "script")
      {
        this._handlePprResponseScript(childNode);
      }
      else if (childNodeName == "script-library")
      {
        this._handlePprResponseLibrary(childNode);
      }
    }
  }
  else if (rootNodeName == "redirect")
  {
    var url = TrPage._getTextContent(documentElement);
    // TODO: fix for portlets???
    window.location.href = url;
  }
  else if (rootNodeName == "error")
  {
    var nodeText = TrPage._getTextContent(documentElement);
    // This should not happen - there should always be an error
    // message
    if (nodeText == null)
      nodeText = "Unknown error during PPR";
    alert(nodeText);
  }  
  else if (rootNodeName == "noop")
  {
    // No op
  }
  else
  {
    // FIXME: log an error
    window.location.reload(true);
  }
}

TrPage.prototype._addResetFields = function(formName, resetNames)
{
  // Create the necessary objects
  var resetFields = this._resetFields;
  if (!resetFields)
  {
    resetFields = new Object();
    this._resetFields = resetFields;
  }

  var formReset = resetFields[formName];
  if (!formReset)
  {
    formReset = new Object();
    resetFields[formName] = formReset;
  }

  // Store "name":true in the map for each such item
  for (var i = 0; i < resetNames.length; i++)
  {
    formReset[resetNames[i]] = true;
  }
}

TrPage.prototype._resetHiddenValues = function(form)
{
  var resetFields = this._resetFields;
  if (resetFields)
  {
    var formReset = resetFields[form.getAttribute("name")];
    if (formReset)
    {
      for (var k in formReset)
      {
        var currField = form[k];
        // Code previously used form.elements.currField
        // on PocketIE.  Dunno why the prior form would ever fail.
        if (!currField)
          currField = form.elements.currField;

        if (currField)
          currField.value = '';
      }
    }
  }
}

/**
 * Add reset callbacks for a form:
 * @param formName the name of the form
 * @param callMap a map from clientId to the JS function
 *   that will reset that component
 */
TrPage.prototype._addResetCalls = function(
  formName,
  callMap)
{
  // Create the necessary objects
  var resetCalls = this._resetCalls;
  if (!resetCalls)
  {
    resetCalls = new Object();
    this._resetCalls = resetCalls;
  }

  var formReset = resetCalls[formName];
  if (!formReset)
  {
    formReset = new Object();
    resetCalls[formName] = formReset;
  }

  // NOTE: this map is never pruned.  Ideally, it would be,
  // but in practice this is unlikely to be a major concern
  for (var k in callMap)
  {
    formReset[k] = callMap[k];
  }
}

/**
 * Callback used by Core.js resetForm() function
 * TODO: remove entire Core.js code, move to public TrPage.resetForm()
 * call.
 */
TrPage.prototype._resetForm = function(form)
{
  var resetCalls = this._resetCalls;
  if (!resetCalls)
    return false;
  var formReset = resetCalls[form.getAttribute("name")];
  if (!formReset)
    return false;

  var doReload = false;
  for (var k in formReset)
  {
    var trueResetCallback = unescape(formReset[k]);
    if (eval(trueResetCallback))
      doReload = true;
  }
  
  return doReload;
}

// TODO move to agent code
TrPage._getNodeName = function(element)
{
  var nodeName = element.nodeName;
  if (!nodeName)
    nodeName = element.tagName;
  return nodeName;
}


// Update the form with the new action provided in the response
TrPage.prototype._handlePprResponseAction = function(contentNode)
{
  var action = contentNode.getAttribute("action");

  if (action)
  {
    var doc = window.document;    

    // Replace the form action used by the next postback
    // Particularly important for PageFlowScope which might
    // change value of the pageflow scope token url parameter.    
    // TODO: track submitted form name at client, instead of
    // just updating the first form
    doc.forms[0].action = action;
  }

  // TODO: support Portal
}

// Handles a single fragment node in a ppr response.
TrPage.prototype._handlePprResponseFragment = function(fragmentNode)
{
  var doc = window.document;
  var targetNode;
  var activeNode;
  var refocusId = null;

  // Due to the limitation of DOM implementation of WM6,
  // a special code is needed here.
  // Note: This code segment is a good candidate to be emcapsulated in to
  // either WM6-specific agent or TrPage subclass.
  if (_agent.isWindowsMobile6)
  {
    // Get the first child node in fragmentNote
    var firstFragmenChildNode = fragmentNode.childNodes[0];
    if (!firstFragmenChildNode)
       return;

    var outerHTML = firstFragmenChildNode.data;

    // Windows Mobile 6 requires the element to be a child of
    // document.body to allow setting its innerHTML property.
    tempDiv = doc.createElement("div");
    tempDiv.id = "tempDiv";
    tempDiv.hidden = "true";

    var bodyElement = doc.body;

    // Temporarily append the new DIV element containing
    // the data in the first child of the fragement to body
    bodyElement.appendChild(tempDiv);
    tempDiv.innerHTML = outerHTML;

    var sourceNode = TrPage._getFirstElementWithId(tempDiv);

    var targetNode = _getElementById(doc, sourceNode.id);
    if (!targetNode)
    {
      return;
    }

    activeNode = _getActiveElement();
    if (activeNode && TrPage._isDomAncestorOf(activeNode, targetNode))
      refocusId = activeNode.id;

    // Insert the new element
    targetNode.parentNode.insertBefore(sourceNode, targetNode);

    // Remove the element to be replaced
    // innderHTML needs to be reset to work around the problem
    // of WM6 DOM. removeChild is not sufficient.
    targetNode.innerHTML = "";
    targetNode.parentNode.removeChild(targetNode);

    // Remove the temporary element
    tempDiv.innerHTML = "";
    bodyElement.removeChild(tempDiv);
  }
  else
  {
    // Convert the content of the fragment node into an HTML node that
    // we can insert into the document
    var sourceNode = this._getFirstElementFromFragment(fragmentNode);

    // In theory, all fragments should have one element with an ID.
    // Unfortunately, the PPRResponseWriter isn't that smart.  If
    // someone calls startElement() with the write component, but never
    // passed an ID, we get an element with no ID.  And, even
    // worse, if someone calls startElement() with a <span> that
    // never gets any attributes on it, we actually strip that
    // span, so we can get something that has no elements at all!
    if (!sourceNode)
       return;

    // Grab the id of the source node - we need this to locate the
    // target node that will be replaced
    var id = sourceNode.getAttribute("id");
    // As above, we might get a node with no ID.  So don't crash
    // and burn, just return.
    if (!id)
      return;

    // Find the target node
    targetNode = _getElementById(doc, id);
    activeNode = _getActiveElement();
    if (activeNode && TrPage._isDomAncestorOf(activeNode, targetNode))
      refocusId = activeNode.id;
    // replace the target node with the new source node
    if (targetNode)
      targetNode.parentNode.replaceChild(sourceNode, targetNode);
  }
  // Call all the DOM replace listeners
  var listeners = this._domReplaceListeners;
  if (listeners)
  {
    for (var i = 0; i < listeners.length; i+=2)
    {
      var currListener = listeners[i];
      var currInstance = listeners[i+1];
      if (currInstance != null)
        currListener.call(currInstance, targetNode, sourceNode);
      else
        currListener(targetNode, sourceNode);
    }
  }

  // TODO: handle nodes that don't have ID, but do take the focus?
  if (refocusId)
  {
    activeNode = doc.getElementById(refocusId);
    if (activeNode && activeNode.focus)
    {
      activeNode.focus();
      window._trActiveElement = activeNode;
    }
  }
}


/**
 * Return true if "parent" is an ancestor of (or equal to) "child"
 */
TrPage._isDomAncestorOf = function(child, parent)
{
  while (child)
  {
    if (child == parent)
      return true;
    var parentOfChild = child.parentNode;
    // FIXME: in DOM, are there ever components whose
    // parentNode is themselves (true for window objects at times)
    if (parentOfChild == child)
      break;
    child = parentOfChild;
  }
  
  return false;
}


/**
 * Replaces the a dom element contained in a peer. 
 * 
 * @param newElement{DOMElement} the new dom element
 * @param oldElement{DOMElement} the old dom element
 */
TrPage.prototype.__replaceDomElement = function(newElement, oldElement)
{
  oldElement.parentNode.replaceChild(newElement, oldElement);
}
  
// Extracts the text contents from a rich response fragment node and 
// creates an HTML element for the first element that is found.
TrPage.prototype._getFirstElementFromFragment = function(fragmentNode)
{
  // Fragment nodes contain a single CDATA section
  var fragmentChildNodes = fragmentNode.childNodes;
  // assert((fragmentChildNodes.length == 1), "invalid fragment child count");

  var cdataNode = fragmentNode.childNodes[0];
  // assert((cdataNode.nodeType == 4), "invalid fragment content");
  // assert(cdataNode.data, "null fragment content");

  // The new HTML content is in the CDATA section.
  // TODO: Is CDATA content ever split across multiple nodes?
  var outerHTML = cdataNode.data;

  // We get our html node by slamming the fragment contents into a div.
  var doc = window.document;  
  var div = doc.createElement("div");

  // Slam the new HTML content into the div to create DOM
  div.innerHTML = outerHTML;
  
  return TrPage._getFirstElementWithId(div);
  
}

// Returns the first element underneath the specified dom node
// which has an id.
TrPage._getFirstElementWithId = function(domNode)
{

  var childNodes = domNode.childNodes;
  var length = childNodes.length;
  
  for (var i = 0; i < length; i++)
  {
    var childNode = childNodes[i];
    if (childNode.id)
    {
      if (!_agent.supportsNodeType)
      {
        return childNode;
      }
      // Check for ELEMENT nodes (nodeType == 1) if nodeType is
      // supported
      else if (childNode.nodeType == 1)
      {
        return childNode;
      }
    }
    return TrPage._getFirstElementWithId(childNode);
  }

  return null;
}

TrPage.prototype._loadScript = function(source)
{
  // Make sure we only load each library once
  var loadedLibraries = this._loadedLibraries;
  if (loadedLibraries[source])
    return;
    
  loadedLibraries[source] = true;
  var xmlHttp = new TrXMLRequest();
  xmlHttp.setSynchronous(true);
  xmlHttp.send(source, null);
  if (xmlHttp.getStatus() == 200)
  {
    var responseText = xmlHttp.getResponseText();
    if (responseText)
    {
      if (_agent.isIE)
        window.execScript(responseText);
      else
        window.eval(responseText);
    }
  }

  // Clean to prevent memory leak
  xmlHttp.cleanup();
}

// Handles a single script node in a rich response
TrPage.prototype._handlePprResponseScript = function(scriptNode)
{
  var source = scriptNode.getAttribute("src");
  if (source)
  {
    this._loadScript(source);
  }
  else
  {
    var nodeText = TrPage._getTextContent(scriptNode);
    if (nodeText)
    {
      if (_agent.isIE)
        window.execScript(nodeText);
      else
        window.eval(nodeText);
    }
  }
}

TrPage.prototype._handlePprResponseLibrary = function(scriptNode)
{
  var nodeText = TrPage._getTextContent(scriptNode);
  this._loadScript(nodeText);
}

// TODO: move to agent API
TrPage._getTextContent = function(element)
{
  if (_agent.isIE)
  {
    // NOTE: this only works if it is an element, not some other DOM node
    var textContent = element.innerText;
    if (textContent == undefined)
      textContent = element.text;
        
    return textContent;
  }

  // Safari doesn't have "innerText", "text" or "textContent" - 
  // (at least not for XML nodes).  So sum up all the text children
  if (_agent.isSafari)
  {
    var text = "";
    var currChild = element.firstChild;
    while (currChild)
    {
      var nodeType = currChild.nodeType;
      if ((nodeType == 3) || (nodeType == 4))
        text = text + currChild.data;
      currChild = currChild.nextSibling;
    }

    return text;
  }

  return element.textContent;
}

TrPage._collectLoadedLibraries = function()
{
  if (!_agent.supportsDomDocument)
  {
    // There is not enough DOM support (e.g. document object does not
    // implement essential properties/methods such as body, documentElement
    // and firstChild) and it is not possible to implement this function.
    return null;
  }
  else
  {
    var loadedLibraries = new Object();

  // We use document.getElementsByTagName() to locate all scripts
  // in the page.  In theory this could be slow if the DOM is huge,
  // but so far seems extremely efficient.
    var domDocument = window.document;
    var scripts = domDocument.getElementsByTagName("script");

    if (scripts != null)
    {
      for (var i = 0; i < scripts.length; i++)
      {
        // Note: we use node.getAttribute("src") instead of node.src as
        // FF returns a fully-resolved URI for node.src.  In theory we could
        // fully resolve/normalize all script src values (both here and
        // in rich responses), but this seems like overkill.  Instead, we
        // just use whatever value happens to show up in the HTML src attribute,
        // whether it is fully resolved or not.  In theory this could mean that
        // we could evalute a library an extra time (if it appears once fully
        // resolved and another time as a relative URI), but this seems like
        // an unlikely case which does not warrant extra code.

        var src = scripts[i].getAttribute("src");

        if (src)
        {
          loadedLibraries[src] = true;
        }
      }
    }
    return loadedLibraries;
  }
}

/**
 * Adds a listener for DOM replacement notification.
 * @param {function} listener listener function to add
 * @param {object} instance to pass as "this" when calling function (optional)
 */
TrPage.prototype.addDomReplaceListener = function(listener, instance)
{
  var domReplaceListeners = this._domReplaceListeners;
  if (!domReplaceListeners)
  {
    domReplaceListeners = new Array();
    this._domReplaceListeners = domReplaceListeners;
  }

  domReplaceListeners.push(listener);
  domReplaceListeners.push(instance);
}

/**
* Removes a listener for DOM replace notifications.
* @param {function} listener  listener function to remove
* @param {object} instance to pass as this when calling function
*/
TrPage.prototype.removeDomReplaceListener = function(listener, instance)
{
  // remove the listener/instance combination
  var domReplaceListeners = this._domReplaceListeners;
  var length = domReplaceListeners.length;
  
  for (var i = 0; i < length; i++)
  {
    var currListener = domReplaceListeners[i];
    i++;
    
    if (currListener == listener)
    {
      var currInstance = domReplaceListeners[i];
      if (currInstance === instance)
      {
        domReplaceListeners.splice(i - 1, 2);
        break;
      }
    }
  }
  
  // remove array, if empty
  if (domReplaceListeners.length == 0)
  {
    this._domReplaceListeners = null;
  }
}
 
/**
 * Adds the styleClassMap entries to the existing internal
 * styleClassMap. Styles can then be accessed via the 
 * getStyleClass function.
 * @param styleClassMap() {key: styleClass, ...}
 */
TrPage.prototype.addStyleClassMap = function(styleClassMap)
{
  if (!styleClassMap)
    return;

  if (!this._styleClassMap)
    this._styleClassMap = new Object();

  // Copy key:styleClass pairs to internal map
  for (var key in styleClassMap)
    this._styleClassMap[key] = styleClassMap[key];
}
 
/**
 * Return the styleClass for the given key.
 * @param key(String) Unique key to retrieve the styleClass
 * @return (String) The styleClass, or undefined if not exist
 */
TrPage.prototype.getStyleClass = function(key)
{
  if (key && this._styleClassMap)
  {
    var mapped = this._styleClassMap[key];
    if (mapped)
      return mapped;
  }

  return key;
}

/**
 * Causes a partial submit to occur on a given component.  The specified
 * component will always be validated first (if appropriate), then optionally 
 * the whole form, prior to submission.
 * @param formId(String) Id of the form to partial submit.
 * @param inputId(String) Id of the element causing the partial submit.  If this
 * element fails validation the partial submit will not be performed.
 * @param event(Event) The javascript event object.
 * @param validateForm(boolean) true if the whole form should be validated.
 */
TrPage._autoSubmit = function(formId, inputId, event, validateForm, params)
{
  if (_agent.isIE)
  {
    // in many forms there is a hidden field named "event"
    // Sometimes IE gets confused and sends us that instead of
    // the true event, so...
    if (event["type"] == "hidden")
      event = window.event;
  }

  // If onchange is used for validation, then first validate 
  // just the current input
  var isValid = true;
  if (_TrEventBasedValidation)
    isValid = _validateInput(event, true);

  // Only proceed if the current input is valid
  if (isValid)
  {
    if (!params)
      params = new Object();
    params.event = "autosub";
    params.source = inputId;
  
    _submitPartialChange(formId, validateForm, params);
  }
}

/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 * 
 *  http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */
function TrStatusIndicator()
{
}

TrStatusIndicator._register = function(id)
{
  if (!TrStatusIndicator._registered)
  {
    TrStatusIndicator._registered = new Object();
    TrPage.getInstance().getRequestQueue().addStateChangeListener(
       TrStatusIndicator._handleStateChange);
  }

  TrStatusIndicator._registered[id] = true;
}


TrStatusIndicator._handleStateChange = function(state)
{
  var busy = state == TrRequestQueue.STATE_BUSY;

  for (id in TrStatusIndicator._registered)
  {
    var busyIcon = document.getElementById(id + "::busy");
    // If we can't find the icon, bail on the assumption that we've
    // been removed (easier than aggressive removal)
    // TODO: in theory, we should be able to remove the property here -
    // but would that break the "for" loop?
    if (!busyIcon)
      continue;

    busyIcon.style.display  = busy ? "inline" : "none";
    var readyIcon = document.getElementById(id + "::ready");
    readyIcon.style.display  = busy ? "none" : "inline";
  }
}


/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */

/**
 * The RequestQueue class is a service to serialize the XML HTTP
 * request communication from the client to the server.
 */
function TrRequestQueue(domWindow)
{
  this._state = TrRequestQueue.STATE_READY;
  this._requestQueue = new Array();
  // listeners that are interested in the state change of this service object
  this._stateChangeListeners = null;
  //this._iframeLoadCallback = undefined;

  // Stash away the DOM window for later reference.
  this._window = domWindow;
}

// Class constants
TrRequestQueue.STATE_READY = 0;
TrRequestQueue.STATE_BUSY = 1;
// frame used for multi-part form post
TrRequestQueue._MULTIPART_FRAME = "_trDTSFrame";

TrRequestQueue._XMLHTTP_TYPE = 0;
TrRequestQueue._MULTIPART_TYPE = 1;

TrRequestQueue.prototype.dispose = function()
{
  // TODO aschwart
  // Check for outstanding requests?
  this._requestQueue = null;
  this._stateChangeListeners = null;
  this._window = null;
}

TrRequestQueue._RequestItem = function(
  type,
  context,
  actionURL,
  headerParams,
  content,
  method
  )
{
  this._type = type;
  this._context = context;
  this._actionURL = actionURL;
  this._headerParams = headerParams;
  this._content = content;
  this._method = method;
}

TrRequestQueue.prototype._broadcastRequestStatusChanged = function(
  context, currMethod, event)
{
  if (currMethod)
  {
    try
    {
      currMethod.call(context, event);
    }
    catch (e)
    {
      TrRequestQueue._logError(
         "Error ", e, " delivering XML request status changed to ",
          currMethod);
    }
  }
}

TrRequestQueue.prototype._addRequestToQueue = function(
  type,
  context,
  listener,
  actionURL,
  content,
  headerParams
  )
{
  var newRequest = new TrRequestQueue._RequestItem(
                          type, context, actionURL, headerParams, content, listener);
  this._requestQueue.push(newRequest);

  try
  {
    var dtsRequestEvent = new TrXMLRequestEvent(
                    TrXMLRequestEvent.STATUS_QUEUED,
                    null); // no xmlhttp object at this time

    this._broadcastRequestStatusChanged(context, listener, dtsRequestEvent);
  }
  catch(e)
  {
    TrRequestQueue._logError("Error on listener callback invocation - STATUS_QUEUED", e);
  }

  if(this._state == TrRequestQueue.STATE_READY)
  {
    this._state = TrRequestQueue.STATE_BUSY;
    this._broadcastStateChangeEvent(TrRequestQueue.STATE_BUSY);
    this._doRequest();
  }
}

//
// HTML-specific API: consider refactoring into a separate class
//
/**
 * Send a form post
 */
TrRequestQueue.prototype.sendFormPost = function(
  context,
  method,
  actionForm,
  params,
  headerParams
  )
{
  //this retrieves the action url for PPR.  Generally this will be the action property on
  //actionForm, however in the case of a Portal 2.0 environment, this could be a special
  //expando property encoded as a ResourceUrl.  As such, if the expando is available, use it
  //for PPR
  var pprURL;
  // In mobile browsers like windows mobile ie, getAttribute funtion throws an exception if 
  // actionForm doesn't contain the attribute "_trinPPRAction".
  try
  {
    pprURL = actionForm.getAttribute("_trinPPRAction");
  }
  catch (e)
  {
  }
  var action = pprURL?pprURL:actionForm.action;
  
  if (this._isMultipartForm(actionForm))
  {
    // TODO: log a warning if we're dropping any headers?  Or
    // come up with a hack to send "headers" via a multipart request?
    this.sendMultipartRequest(context, method, action, actionForm, params);
  }
  else
  {
    var content = this._getPostbackContent(actionForm, params);

    // IE BUG, see TRINIDAD-704
    if(_agent.isIE)
      this._autoCompleteForm(actionForm);

    this.sendRequest(context, method, action, content, headerParams);
  }
}

/**
 * Internet Explorer has a bug, that the autocomplete does not work when
 * using JavaScript to submit a form.
 */
TrRequestQueue.prototype._autoCompleteForm = function(form)
{
  var theExternal = window.external;

  if (theExternal && (typeof theExternal.AutoCompleteSaveForm != "undefined"))
  {
    try
    {
      theExternal.AutoCompleteSaveForm(form);
    }
    catch (e)
    {
      // ignore
    }
  }
}

/**
 * Returns true if the form has a "file" input that contains
 * anything to upload.
 */
TrRequestQueue.prototype._isMultipartForm = function(actionForm)
{
  // If there not enough DOM support, namely getElementsByTagName() being
  // not supported, this function does not work. Return false for such case.
  if (!_agent.supportsDomDocument)
  {
    return false;
  }
  // Use enctype - supported on IE >= 6, Moz, and Safari.
  // encoding is not supported on Safari.
  if (actionForm.enctype.toLowerCase() != "multipart/form-data")
    return false;

  var inputs = actionForm.getElementsByTagName("input"),
      inputCount = inputs.length, multiPartForm = null;

  for (var i = 0; i < inputCount; ++i)
  {
    var inputElem = inputs[i];
    if (inputElem.type == "file" && inputElem.value)
    {
      return true;
    }
  }

  return false;
}

/**
 * Returns the payload to include in the rich postback
 * @param actionForm {Element} Form to build up input elements for post in
 * @param params {Object} Name/value pairs to ensure that the form contains
 * @return Content encoded correctly in String form
 */
TrRequestQueue.prototype._getPostbackContent = function(actionForm, params)
{
  var formElements = actionForm.elements;

  // 1. build up formParams
  var formParams = {};
  if (formElements)
  {
    for (var elementIndex = 0; elementIndex < formElements.length; elementIndex++)
    {
      var input = formElements[elementIndex];

      // todo: do not post values for non-triggering submit buttons
      // TRINIDAD-874 skip input.type="submit" fields
      if (input.name && !input.disabled && !(input.tagName=="INPUT" && input.type=="submit"))
      {
        if (input.options)
        {
          formParams[input.name] = new Array();
          for (var j=0; j < input.options.length; j++)
          {
            var option = input.options[j];
            if (option.selected)
            {
              var optionValue = (option.value === null) ?
                 option.text : option.value;
              formParams[input.name].push(optionValue);
            }
          }
        }
        // if this happens to be an unselected checkbox or radio then
        // skip it. Otherwise, if it is any other form control, or it is
        // a selected checkbox or radio than add it:
        else if (!((input.type == "checkbox" ||
                    input.type == "radio") &&
                   !input.checked))
        {
          // the value might already have been set (because of a
          // multi-select checkbox group:
          var current = formParams[input.name];
          if (current)
          {
            // the value has already been set, so we need to create an array
            // and push both values into the array.
            // first check to see if we already have an array:
            if (!current.join)
            {
              // we don't have an array so create one:
              var list = new Array();
              list.push(current);
              formParams[input.name] = list;
              current = list;
            }
            // we already have an array, so add the new value to the array:
            current.push(input.value);
          }
          else
          {
            formParams[input.name] = input.value;
          }
        }
      }
    }
  }

  // 2. override formParams with params
  for (var key in params)
  {
    var value = params[key];
    formParams[key] = params[key];
  }

  // 3. create form submit payload
  var content = "";
  for (var key in formParams)
  {
    var paramValue = formParams[key];
    if (paramValue != null)
    {
      // If it's an array...
      if (paramValue.join)
      {
        var array = paramValue;
        for(var i=0; i < array.length; i++)
        {
          content = TrRequestQueue._appendUrlFormEncoded(content, key, array[i]);
        }
      }
      else
      {
        content = TrRequestQueue._appendUrlFormEncoded(content, key, paramValue);
      }
    }
  }
  return content;
}


TrRequestQueue._appendUrlFormEncoded = function(
  buffer,
  key,
  value)
{
  if (buffer.length > 0)
  {
    buffer = buffer + "&";
  }

  return buffer + key + "=" + value.toString().replace(/\%/g, '%25')
                                           .replace(/\+/g, '%2B')
                                           .replace(/\//g, '%2F')
                                           .replace(/\&/g, '%26')
                                           .replace(/\"/g, '%22')
                                           .replace(/\'/g, '%27');
}

//
// Generic API
//

/**
* Performs Asynchronous XML HTTP Request with the Server
* @param context    any object that is sent back to the callback when the request
*  is complete. This object can be null.
* @param method   Javascript method
* @param actionURL   the url to send the request to
* @param headerParams  Option HTTP header parameters to attach to the request
* @param content     the content of the Asynchronous XML HTTP Post
*/
TrRequestQueue.prototype.sendRequest = function(
  context,
  method,
  actionURL,
  content,
  headerParams
  )
{
  this._addRequestToQueue(TrRequestQueue._XMLHTTP_TYPE, context, method, actionURL, content, headerParams);
}

/**
* Performs Asynchronous HTTP Request with the Server for multipart data
* @param context    any object that is sent back to the callback when the request
*  is complete. This object can be null.
* @param actionURL  this is the appropriate action url
* @param htmlForm    the form containing multi-part data. The action attribute
*   of the form is used for send the request to the server
* @param params     additional parameters that need to be sent to the server
* @param method   Javascript method
*/
TrRequestQueue.prototype.sendMultipartRequest = function(
  context,
  method,
  actionURL,
  htmlForm,
  params
  )
{
  var privateContext =
     {"htmlForm":htmlForm, "params": params, "context": context, "method": method};

  this._addRequestToQueue(TrRequestQueue._MULTIPART_TYPE, privateContext, null, actionURL);
}


TrRequestQueue.prototype._doRequest = function()
{
  // currently we are posting only one request at a time. In future we may batch
  // mutiple requests in one post
  var requestItem = this._requestQueue.shift();
  switch (requestItem._type)
  {
    case TrRequestQueue._XMLHTTP_TYPE:
      this._doXmlHttpRequest(requestItem);
      break;

    case TrRequestQueue._MULTIPART_TYPE:
      this._doRequestThroughIframe(requestItem);
      break;
  }
}

TrRequestQueue.prototype._doXmlHttpRequest = function(requestItem)
{
  var xmlHttp = new TrXMLRequest();
  xmlHttp.__dtsRequestContext = requestItem._context;
  xmlHttp.__dtsRequestMethod = requestItem._method;
  var callback = TrUIUtils.createCallback(this, this._handleRequestCallback);
  xmlHttp.setCallback(callback);

  // xmlhttp request uses the same charset as its parent document's charset.
  // There is no need to set the charset.
  xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");

  var headerParams = requestItem._headerParams;

  if (headerParams != null)
  {
    for (var headerName in headerParams)
    {
      var currHeader =  headerParams[headerName];

      // handle array parameters by joining them together with comma separators
      // Test if it's an array via the "join" method
      if (currHeader["join"])
        currHeader = currHeader.join(',')

      xmlHttp.setRequestHeader(headerName, currHeader);
    }
  }

  xmlHttp.send(requestItem._actionURL, requestItem._content);
}

TrRequestQueue.prototype._doRequestThroughIframe = function(requestItem)
{
  var htmlForm  = requestItem._context.htmlForm;
  var actionURL = requestItem._actionURL;
  var params  = requestItem._context.params;
  // assert(htmlForm.action, "form action cannot be null for multiform post");
  var frameName = TrRequestQueue._MULTIPART_FRAME;
  var domDocument = this._getDomDocument();
  var hiddenFrame = domDocument.getElementById(frameName), iframeDoc;
  var agentIsIE = _agent.isIE;
  if(!hiddenFrame)
  {
    hiddenFrame = domDocument.createElement('iframe');
    hiddenFrame.name = frameName;
    hiddenFrame.id = frameName;
    var frameStyle = hiddenFrame.style;
    frameStyle.top = frameStyle.left = '0px';
    frameStyle.width = frameStyle.height = '1px'
    frameStyle.position = 'absolute';
    frameStyle.visibility = "hidden";

    domDocument.body.appendChild(hiddenFrame);
  }

  if (agentIsIE)
  {
    // Why these lines happen to work, I can't say - but remove them,
    // and the postback actually goes to a new window
    hiddenFrame = domDocument.frames[frameName];
    hiddenFrame.name = frameName;
    iframeDoc = hiddenFrame.document;
  }
  else if (_agent.isSafari)
  {
    iframeDoc = hiddenFrame.document;
  }
  else
  {
    iframeDoc = hiddenFrame.contentDocument;
  }

  // We may not have a document yet for the IFRAME, since
  // nothing has been loaded (appears to work this way on Safari)
  if (iframeDoc && iframeDoc.firstChild)
    iframeDoc.removeChild(iframeDoc.firstChild);

  // store our context variables for later use
  this._dtsContext = requestItem._context.context;
  this._dtsRequestMethod = requestItem._context.method;
  this._htmlForm = htmlForm;
  this._savedActionUrl = htmlForm.action;
  this._savedTarget = htmlForm.target;

  // FIXME: why are the next two lines at all necessary?  The form
  // should already be set to post, and if the action has been
  // updated since this was queued
  htmlForm.method = "POST";
  htmlForm.action = actionURL;

  htmlForm.target = frameName;

  this._appendParamNode(domDocument, htmlForm, "Tr-XHR-Message", "true");
  // FIXME: the "partial" parameter is unnecessary
  this._appendParamNode(domDocument, htmlForm, "partial", "true");

  if(params)
  {
    for (var key in params)
    {
      this._appendParamNode(domDocument, htmlForm, key, params[key]);
    }
  }

  if(this._iframeLoadCallback == null)
    this._iframeLoadCallback = TrUIUtils.createCallback(this, this._handleIFrameLoad);

  // IE BUG, see TRINIDAD-704
  if(_agent.isIE)
    this._autoCompleteForm(htmlForm);

  try
  {
    htmlForm.submit();
  }
  catch (e)
  {
    if (this._isMultipartForm(htmlForm))
    {
      // IE will fail on an input file submission of a file that does not exist
      var facesMessage = _createFacesMessage(
        'org.apache.myfaces.trinidad.component.core.input.CoreInputFile.INPUT_FILE_ERROR');
      // if there's nowhere to display the message in either
      // summary or detail, then pop an alert to warn the page developer
      if (!TrMessageBox.isPresent())
        alert(facesMessage.getDetail());
      else
        // Add the message to the MessageBox
        TrMessageBox.addMessage(null, null, facesMessage);
    }
    else
    {
      throw e;
    }
  }

  this._window.setTimeout(this._iframeLoadCallback, 50);
}

TrRequestQueue.prototype._appendParamNode = function(domDocument, form, name, value)
{
  // assert(form!=null);

  var nodes = this._paramNodes;

  if(!nodes)
  {
    nodes = new Array();
    this._paramNodes = nodes;
  }

  var node = domDocument.createElement("input");
  node.type = "hidden";
  node.name = name;
  node.value = value;
  nodes.push(node);
  form.appendChild(node);
}

TrRequestQueue.prototype._clearParamNodes = function()
{
  var nodes = this._paramNodes;

  if(nodes)
  {
    var form = nodes[0].parentNode;
    var count = nodes.length;

    for (var i = 0; i < count; i++)
    {
      form.removeChild(nodes[i]);
    }

    delete this._paramNodes;
  }
}

TrRequestQueue.prototype._handleIFrameLoad = function()
{
  var domDocument = this._getDomDocument();
  var agentIsIE = _agent.isIE;
  var frameName = TrRequestQueue._MULTIPART_FRAME;
  var hiddenFrame, iframeDoc;
  if (agentIsIE)
  {
    hiddenFrame = domDocument.frames[frameName];
    var iframeDoc = hiddenFrame.document;
  }
  else
  {
    hiddenFrame = domDocument.getElementById(frameName);
    iframeDoc = hiddenFrame.contentDocument;
  }

  try
  {
    if(!iframeDoc.documentElement || !iframeDoc.documentElement.firstChild
      || (agentIsIE && iframeDoc.readyState != "complete"))
    {
      this._window.setTimeout(this._iframeLoadCallback, 50);
    }
    else
    {
      this._onIFrameLoadComplete(iframeDoc, this._dtsContext,
                                 this._dtsRequestMethod);
    }
  }
  catch(e)
  {
    TrRequestQueue._alertError();
    TrRequestQueue._logError("Error while performing request", e);

    this._htmlForm.action = this._savedActionUrl;
    this._htmlForm.target = this._savedTarget;
  }
}

TrRequestQueue.prototype._onIFrameLoadComplete = function(
  iframeDoc,
  context,
  requestMethod)
{
  try
  {
    var dtsRequestEvent = new TrIFrameXMLRequestEvent(
                              iframeDoc);

    this._broadcastRequestStatusChanged(context, requestMethod,dtsRequestEvent);
  }
  finally
  {
    //cleanup
    if(iframeDoc.firstChild)
      iframeDoc.removeChild(iframeDoc.firstChild);
    this._htmlForm.action = this._savedActionUrl;
    this._htmlForm.target = this._savedTarget;
    //clear the parameter nodes
    this._clearParamNodes();
    this._requestDone();
  }
}

TrRequestQueue.prototype._handleRequestCallback = function(
  xmlHttp
  )
{
  var httpState = xmlHttp.getCompletionState();

  if(httpState != TrXMLRequest.COMPLETED)
    return;

  var statusCode = 0;
  var failedConnectionText = TrRequestQueue._getFailedConnectionText();
  try
  {
    statusCode = xmlHttp.getStatus();
  }
  catch(e)
  {
    // Drop the exception without logging anything.
    // Firefox will throw an exception on attempting
    // to get the status of an XMLHttpRequest if
    // the Http connection  has been closed
  }

  if ((statusCode != 200) && (statusCode != 0))
  {
    TrRequestQueue._alertError();
    TrRequestQueue._logError("Error StatusCode(",
                         statusCode,
                          ") while performing request\n",
                         xmlHttp.getResponseText());
  }

  try
  {
    if (statusCode != 0)
    {
      var dtsRequestEvent = new TrXMLRequestEvent(
                  TrXMLRequestEvent.STATUS_COMPLETE,
                  xmlHttp);
      this._broadcastRequestStatusChanged(
        xmlHttp.__dtsRequestContext,
        xmlHttp.__dtsRequestMethod,
        dtsRequestEvent);
    }
  }
  finally
  {
    //cleanup
    xmlHttp.cleanup();
    delete xmlHttp;
    this._requestDone();
  }
}

TrRequestQueue.prototype._requestDone = function()
{
  if(this._requestQueue.length > 0)
  {
    // send the next one in the queue
    this._doRequest();
  }
  else
  {
    // Reset our state to recieve more requests
    this._state = TrRequestQueue.STATE_READY;
    this._broadcastStateChangeEvent(TrRequestQueue.STATE_READY);
  }
}


/**
* Adds a listener to the request queue that is interested in its state change.
* The listners are notified in the order that they are added. A listener can cancel
* notification to other listeners in the chain by returning false.
*
* @param {function} listener  listener function to remove
* @param {object} instance to pass as this when calling function
*/
TrRequestQueue.prototype.addStateChangeListener = function(listener, instance)
{
  // assertFunction(listener);
  // assertObjectOrNull(instance);

  var stateChangeListeners = this._stateChangeListeners;

  if (!stateChangeListeners)
  {
    stateChangeListeners = new Array();
    this._stateChangeListeners = stateChangeListeners;
  }

  stateChangeListeners.push(listener);
  stateChangeListeners.push(instance);
}

/**
* Removes a listener from the request queue that is interested in its state change.
* @param {function} listener  listener function to remove
* @param {object} instance to pass as this when calling function
*/
TrRequestQueue.prototype.removeStateChangeListener = function(listener, instance)
{
  // assertFunction(listener);
  // assertObjectOrNull(instance);

  // remove the listener/instance combination
  var stateChangeListeners = this._stateChangeListeners;

  // assert(stateChangeListeners, "stateChangeListeners must exist");

  var length = stateChangeListeners.length;

  for (var i = 0; i < length; i++)
  {
    var currListener = stateChangeListeners[i];
    i++;

    if (currListener == listener)
    {
      var currInstance = stateChangeListeners[i];

      if (currInstance === instance)
      {
        stateChangeListeners.splice(i - 1, 2);
      }
    }
  }

  // remove array, if empty
  if (stateChangeListeners.length == 0)
  {
    this._stateChangeListeners = null;
  }
}

/**
 * Return the current DTS state.
 * return (int) _state
 */
TrRequestQueue.prototype.getDTSState = function()
{
  return this._state;
}

/**
 * broadcast the state change of the request queue to its listeners
 */
TrRequestQueue.prototype._broadcastStateChangeEvent = function(state)
{
  var stateChangeListeners = this._stateChangeListeners;

  // deliver the state change event to the listeners
  if (stateChangeListeners)
  {
    var listenerCount = stateChangeListeners.length;

    for (var i = 0; i < listenerCount; i++)
    {
      try
      {
        var currListener = stateChangeListeners[i];
        i++;
        var currInstance = stateChangeListeners[i];

        if (currInstance != null)
          currListener.call(currInstance, state);
        else
          currListener(state);
      }
      catch (e)
      {
        TrRequestQueue._logError("Error on DTS State Change Listener", e);
      }
    }
  }
}

TrRequestQueue.prototype._getDomDocument = function()
{
  return this._window.document;
}

TrRequestQueue._getFailedConnectionText = function()
{
  // TODO: get translated connection information
  return "Connection failed";
}

TrRequestQueue._alertError = function()
{
  // TODO: get translated connection information
  var failedConnectionText = TrRequestQueue._getFailedConnectionText();
  if (failedConnectionText != null)
    alert(failedConnectionText);

}

// Logging helper for use in Firebug
TrRequestQueue._logWarning = function(varArgs)
{
  if (window.console && console.warn)
    console.warn(arguments);
  // else???
}

// Logging helper for use in Firebug
TrRequestQueue._logError = function(varArgs)
{
  if (window.console && console.error)
    console.error(arguments);
  // else???
}

/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 * 
 *  http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */


/**
* TrXMLRequest class is a low-level XML HTTP Request
* wrapper
**/
/**
* Default constructor. Creates an asynchronous XML HTTP request
**/
function TrXMLRequest()
{
  this.isSynchronous = false;
  this.callback = null;
  this._state = TrXMLRequest.UNINITIALIZED;
  this.headers = new Object();
  this.xmlhttp = TrXMLRequest._createXmlHttpRequest();
}


/**
* Request state constants. See getCompletionState()
**/
TrXMLRequest.UNINITIALIZED = 0;
TrXMLRequest.LOADING = 1;
TrXMLRequest.LOADED = 2;
TrXMLRequest.INTERACTIVE = 3;
TrXMLRequest.COMPLETED = 4;

/**
* Specifies whether the request should be synchronous
* Parameters: isSynch - true if request should be synchronous,
* false otherwise
**/
TrXMLRequest.prototype.setSynchronous = 
function (isSynch)
{
  this.isSynchronous = isSynch;
};

/**
* Registers request callback for asynchronous requests
* The callback will be called each time the state of the
* request changes (see getCompletionState())
* The callback should have the following siganture:
* <void> function (<TrXMLRequest>request)
**/
TrXMLRequest.prototype.setCallback = 
function (_callback)
{
  this.callback = _callback;
};

/**
* Returns request's completion state (see Request state
* constants)
**/
TrXMLRequest.prototype.getCompletionState =
function()
{
  return this._state;
};

/**
 * Returns the HTTP response status.  For example, 200 is OK.
 */
TrXMLRequest.prototype.getStatus =
function()
{
  return this.xmlhttp.status;
}

/**
* Returns the response as an XML document
* Note: this method will block if the the request is asynchronous and
* has not yet been completed
**/
TrXMLRequest.prototype.getResponseXML = 
function()
{
  return this.xmlhttp.responseXML;
}

/**
* Returns the response as text
* Note: this method will block if the the request is asynchronous and
* has not yet been completed
**/
TrXMLRequest.prototype.getResponseText = 
function()
{
  return this.xmlhttp.responseText;
}

/**
* Sends an XML HTTP request
* Parameters: 
* url - destination URL
* content - XML document or string that should be included in the request's body
**/
TrXMLRequest.prototype.send =
function(url, content)
{
  var xmlhttp = this.xmlhttp;
  if (!this.isSynchronous)
  {
    var cb = new Function("arguments.callee.obj.__onReadyStateChange();");
    cb.obj = this;
    xmlhttp.onreadystatechange  = cb;
  }
  
  var method = content ? "POST" : "GET";
  xmlhttp.open(method, url, !this.isSynchronous);
  for (var name in this.headers)
    xmlhttp.setRequestHeader(name, this.headers[name]);

  // Set some header to indicate the request initiated from
  // the Trinidad XHR request
  // =-ags This needs to be revisited
  xmlhttp.setRequestHeader("Tr-XHR-Message", "true");

  xmlhttp.send(content);
  if (this.isSynchronous)
  {
    this._state = xmlhttp.readyState;
  }
}


TrXMLRequest.prototype.getResponseHeader = 
function(name)
{

  return this.xmlhttp.getResponseHeader(name);
}

TrXMLRequest.prototype.getAllResponseHeaders = 
function()
{
  return this.xmlhttp.getAllResponseHeaders();
}

TrXMLRequest.prototype.setRequestHeader = 
function(name, value)
{
  this.headers[name] = value;
}

TrXMLRequest._createXmlHttpRequest = function()
{
  var xmlhttp;

  if (window.XMLHttpRequest)
  {
    xmlhttp = new XMLHttpRequest();
  }
  else if (window.ActiveXObject)
  {
    try
    {
      xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
    }
    catch (e)
    {
      try
      {
        xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
      }
      catch (e)
      {
      }
    }
  }
  return xmlhttp;
}


TrXMLRequest.prototype.__onReadyStateChange =
function()
{
  this._state = this.xmlhttp.readyState;
  if (this.callback)
    this.callback(this);
}

TrXMLRequest.prototype.cleanup =function()
{
  this.callback = null
  delete this.xmlhttp;
}



/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 * 
 *  http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */

/**
 * AJAX Request Event class. This object is passed back to the listeners
 * of a AJAX Service Request. It support ITrXMLRequestEvent pseudo-interface
 * with the following methods: getStatus, getResponseXML, getResponseText, 
 * isPprResponse, getResponseContentType
 */
function TrXMLRequestEvent(
  status,
  request
  )
{
  this._status = status;
  this._request = request;
}

TrXMLRequestEvent.STATUS_QUEUED = 1;
TrXMLRequestEvent.STATUS_SEND_BEFORE = 2;
TrXMLRequestEvent.STATUS_SEND_AFTER = 3;
TrXMLRequestEvent.STATUS_COMPLETE = 4;

TrXMLRequestEvent.prototype.getStatus = function()
{
  return this._status;
}

/**
* Returns the response of the AJAX Request as an XML document
* NOTE: this method is valid only for TrXMLRequestEvent.STATUS_COMPLETE
**/
TrXMLRequestEvent.prototype.getResponseXML = function()
{
  return this._request.getResponseXML();
}

/**
* Returns true if the response XML of the AJAX Request is valid.
* NOTE: this method is valid only for TrXMLRequestEvent.STATUS_COMPLETE
**/
TrXMLRequestEvent.prototype._isResponseValidXML = function()
{         
  // Note: Mozilla applies default XSLT to XML parse error
  var responseDocument = this._request.getResponseXML();
  if (!responseDocument)
    return false;

  var docElement = responseDocument.documentElement;
  if (!docElement)
    return false;

  var nodeName = docElement.nodeName;
  if (!nodeName)
    nodeName = docElement.tagName;

  if (nodeName == "parsererror")
    return false;

  return true;
}

/**
* Returns the response of the AJAX Request as text.
* NOTE: this method is valid only for TrXMLRequestEvent.STATUS_COMPLETE
**/
TrXMLRequestEvent.prototype.getResponseText = function()
{  
  return this._request.getResponseText();
}

/**
* Returns the status code of the xml http AJAX Request.
* NOTE: this method is valid only for TrXMLRequestEvent.STATUS_COMPLETE
**/
TrXMLRequestEvent.prototype.getResponseStatusCode = function()
{
  return this._request.getStatus();
}

/**
* Returns all the response headers for xml http AJAX Request.
* NOTE: this method is valid only for TrXMLRequestEvent.STATUS_COMPLETE
**/
TrXMLRequestEvent.prototype._getAllResponseHeaders = function()
{
  return this._request.getAllResponseHeaders();
}

/**
* Returns a particular response header for xml http Request.
* NOTE: this method is valid only for TrXMLRequestEvent.STATUS_COMPLETE
**/
TrXMLRequestEvent.prototype.getResponseHeader = function(
  name
  )
{
  // Note: Mozilla chokes when we ask for a response header that does not exist
  var allHeaders = this._request.getAllResponseHeaders();
  return (allHeaders.indexOf(name) != -1) ?
           this._request.getResponseHeader(name)
           : null;
}

/**
* Returns if whether if is a rich response
* NOTE: this method is valid only for TrXMLRequestEvent.STATUS_COMPLETE
**/
// TODO: this should likely be deleted or renamed, as it is
// not PPR-specific here
TrXMLRequestEvent.prototype.isPprResponse = function()
{
  // todo: do we need to mark rich responses?
  // var responseType = this.getResponseHeader("Tr-XHR-Response-Type");
  var isrich = true;
  if (isrich && (!this._isResponseValidXML()))
  {
    TrRequestQueue._logError("Invalid PPR response."+
        " The response-headers were:\n"+
        this._getAllResponseHeaders() +
        "\n The invalid response was:\n"+
        this.getResponseText());
  }
  return isrich;
}

/**
* Returns if whether if is a rich response
* NOTE: this method is valid only for TrXMLRequestEvent.STATUS_COMPLETE
**/
TrXMLRequestEvent.prototype.getResponseContentType = function()
{
  this.getResponseHeader("Content-Type");
}
/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 * 
 *  http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */

/**
 * Iframe base Data Transfer Request Event class. This object is passed back to the listeners
 * of a Data Transfer Service Request. This object and TrXMLRequestEvent 
 * support ITrXMLRequestEvent pseudo-interface
 * @ see TrXMLRequestEvent
 */
function TrIFrameXMLRequestEvent(
  iframeDoc)
{
  this._iframeDoc = iframeDoc;
}


TrIFrameXMLRequestEvent.prototype.getStatus = function()
{
  // If we go this far the transfer is complete
  return TrXMLRequestEvent.STATUS_COMPLETE;
}


/**
* Returns the response of the Data Transfer Request as an XML document
* NOTE: this method is valid only for TrXMLRequestEvent.STATUS_COMPLETE
**/
TrIFrameXMLRequestEvent.prototype.getResponseXML = function()
{
  var agentIsIE = _agent.isIE;
  var iframeDoc = this._iframeDoc;
  if(agentIsIE && iframeDoc.XMLDocument)
    return iframeDoc.XMLDocument;
  else
    return iframeDoc;
}


/**
* Returns the response of the Data Transfer Request as text.
* NOTE: this method is valid only for TrXMLRequestEvent.STATUS_COMPLETE
**/
TrIFrameXMLRequestEvent.prototype.getResponseText = function()
{  
  var agentIsIE = _agent.isIE;
  var iframeDoc = this._iframeDoc, xmlDocument = null;

  if(agentIsIE && iframeDoc.XMLDocument)
    xmlDocument = iframeDoc.XMLDocument;
  else if(window.XMLDocument && (iframeDoc instanceof XMLDocument))
    xmlDocument = iframeDoc;
    
  if(xmlDocument)
    return AdfAgent.AGENT.getNodeXml(xmlDocument);
  else
    return iframeDoc.documentElement.innerHTML;
}

TrIFrameXMLRequestEvent.prototype._isResponseValidXML = function()
{
  var agentIsIE = _agent.isIE;
  var iframeDoc = this._iframeDoc;

  if(agentIsIE && iframeDoc.XMLDocument)
    return true;
  else if(window.XMLDocument && (iframeDoc instanceof XMLDocument))
    return true;
  else
    return false;
}

/**
* Returns the status code of the xml http Data Transfer Request.
* NOTE: this method is valid only for TrXMLRequestEvent.STATUS_COMPLETE
**/
TrIFrameXMLRequestEvent.prototype.getResponseStatusCode = function()
{
  // if we got till here using Iframe we must have document so return 200
  return 200;
}

/**
* Returns if whether if is a rich response
* NOTE: this method is valid only for TrXMLRequestEvent.STATUS_COMPLETE
**/
TrIFrameXMLRequestEvent.prototype.isPprResponse = function()
{
  var agentIsIE = _agent.isIE;
  var iframeDoc = this._iframeDoc;
  var isRichReponse = false;
  
  // Look for "Adf-Rich-Response-Type" PI
  if(agentIsIE && iframeDoc.XMLDocument)
  {
    var xmlDocument = iframeDoc.XMLDocument, childNodes = xmlDocument.childNodes;
    // In IE the xml PI is the first node
    if(childNodes.length >= 2 && childNodes[1].nodeName ==  "Tr-XHR-Response-Type")
      isRichReponse = true;
  }
  else
  {
    if(iframeDoc.firstChild && iframeDoc.firstChild.nodeName ==  "Tr-XHR-Response-Type")
      isRichReponse = true;
  }
  
  return isRichReponse;
}

/**
* Returns if whether if is a rich response
* NOTE: this method is valid only for TrXMLRequestEvent.STATUS_COMPLETE
**/
TrIFrameXMLRequestEvent.prototype.getResponseContentType = function()
{
  if(this._isResponseValidXML())
    return "text/xml";
    
  return "text/html";
}
