/**
 * File         : dom.js
 * Created      : 20070910
 * Author       : JT Best
 * Organisation : Southstar Computers Limited
 * Copyright    : (c) 2007, Southstar Computers Limited
 * Purpose      : Implement a Javascript DOM Abstraction Layer
 * Dependencies : wz_tooltip.js [Optional] (http://www.walterzorn.com/tooltip/tooltip_e.htm)
 * Design       : Dates are specified in yyyymmdd ISO format.
 * Changes      : NONE
 */

/******************************
** Utility Functions and stubs
*******************************/
// The following are place-holders. When the Southstar Calendar Gadget is used with
// Walter Zorn's Tooltip library, it should override these definitions and the
// calls to Tip() embedded in the onmouseover handler for each registered
// date will use his definition to pop up a tooltip containing the formatted
// text of the diarydate entry in the body of the document.
// When Walter Zorn's library is not in use, the default "title" property 
// will cause a simple, one-line tooltip to be displayed when the mouse hovers
// over a registered date.
// NB. wz_tooltip.js is required for the Help functions to work!
TagToTip = function () { }
Tip = function () { }
var TITLE
var CLICKCLOSE;
var CLOSEBTN;
var STICKY;
var WIDTH;


function debug( strText ) {
	//document.writeln( "<p>" + strText + "</p>" );  // Don't use writeln - breaks in XML!!!
}

function clone( objToClone ) {
  if ( objToClone == null ) return  null;

  var objNew = new objToClone.constructor;
  for ( var varMember in objToClone ) {
    var strType = typeof objToClone[ varMember ];
    if (typeof objToClone[ varMember ] == 'object' ) {
      objNew [ varMember ] = clone( objToClone[ varMember ] );
    } else {
      objNew[ varMember ] = objToClone[ varMember ];
    }
  }
  return objNew;
}

function copyAttributes( objTarget, objSource ) {
  if ( objSource && objTarget ) {
    for ( var strAttribute in objSource ) {
      objTarget[ strAttribute ] = objSource[ strAttribute ];
    }
  }
}

function createButton( objParent, strLabel, objAttributes, objMembers, strImagePath, strImageWidth, strImageHeight, objListeners ) {
  var objTag;
  if ( objParent ) {
    objTag = dom.createElement( objParent, "button", objAttributes, strLabel ); 
    if (objMembers) {
      for ( var strMember in objMembers ) {
        eval( "objTag." + strMember + " = objMembers[ strMember ]" );
      }
    }
    if (strImagePath && (strImagePath != "")) {
      dom.createElement( objTag, "img", { 'src' : strImagePath, 'width': strImageWidth, 'height': strImageHeight });
    }
    if (objListeners) {
  	  for ( var strEvent in objListeners ) {
  	    dom.registerListener( objTag, strEvent, objListeners[ strEvent ], false );
  	  }
  	}
  }
  return objTag;
}

function treeSet( objRoot, strAttribute, strRelativeOperator, strTestValue, strNewValue, strSafeAttribute ) {
	// Recurse through a tree setting the given attribute to the new value where the test rule succeeds
	var strEval;
	if ( objRoot ) {
		if ( objRoot.childNodes ) {
			for ( varIndex in objRoot.childNodes ) {     // Recurse through child nodes
				treeSet( objRoot.childNodes[ varIndex ], strAttribute, strRelativeOperator, strTestValue, strNewValue );
			}
		}
		if ( strSafeAttribute ) {                      // Put attribute value in safe, if "safe" specified
			strEval = "objRoot." + strSafeAttribute + " = objRoot." + strAttribute;
			eval (strEval );
		}
		if ( strRelativeOperator && strTestValue ) {   // If rule, apply and if true, assign new value to attribute
			strEval = "if ( objRoot." + strAttribute + " " + strRelativeOperator + " strTestValue " + " ) {"
			        + " objRoot." + strAttribute + " = strNewValue; "
							+ "}";
		  eval( strEval );
		} else {                                       // Otherwise, just assign new value
			strEval = "objRoot." + strAttribute + " = strNewValue";
			eval( strEval );
		}
	}
}

/******************************************************************************************
 * Constants
 */
if (!window.Node) var Node =
  {
    ELEMENT_NODE                :  1,
    ATTRIBUTE_NODE              :  2,
    TEXT_NODE                   :  3,
    CDATA_SECTION_NODE          :  4,
    ENTITY_REFERENCE_NODE       :  5,
    ENTITY_NODE                 :  6,
    PROCESSING_INSTRUCTION_NODE :  7,
    COMMENT_NODE                :  8,
    DOCUMENT_NODE               :  9,
    DOCUMENT_TYPE_NODE          : 10,
    DOCUMENT_FRAGMENT_NODE      : 11,
    NOTATION_NODE               : 12
  }

/******************************************************************************************
** Node Object
*******************************************************************************************/
if ( typeof Node == "undefined" ) {
  var Node = {
    ELEMENT_NODE                : 1,
    ATTRIBUTE_NODE              : 2,
    TEXT_NODE                   : 3,
    CDATA_SECTION_NODE          : 4,
    ENTITY_REFERENCE_NODE       : 5,
    ENTITY_NODE                 : 6,
    PROCESSING_INSTRUCTION_NODE : 7,
    COMMENT_NODE                : 8,
    DOCUMENT_NODE               : 9,
    DOCUMENT_TYPE_NODE          : 10,
    DOCUMENT_FRAGMENT_NODE      : 11,
    NOTATION_NODE               : 12
  }
}


/******************************************************************************************
** Point Object
*******************************************************************************************/
function Point( ) {
  this.strException = "";
  if ( arguments.length > 0 ) {
    switch ( arguments.length ) {
      case 1:
        var varArgument = arguments[ 0 ];
        switch ( varArgument.constructor ) {
          case Array:
            this.intX = varArgument[ 0 ];
            this.intY = varArgument[ 1 ];
          default:
            if ( varArgument.x != undefined  && varArgument.y != undefined ) {
              this.intX = varArgument.x;
              this.intY = varArgument.y;
            }
/*
             else {
              var pntElement = dom.getTopLeft( varArgument, true );
              this.intX = pntElement.x;
              this.intY = pntElement.y;
            } 
*/
            break;
        }
        break;
      case 2:
        this.intX = arguments[ 0 ];
        this.intY = arguments[ 1 ];
        break;
      default:
        // More than 2 arguments is an exception
        this.strException = "Point cannot be constructed using more than two arguments";
        break;
    }
  }
}

Point.prototype.asOffsetFrom = function( v_obj ) {
  if ( v_obj.constructor == Point ) {
    this.intX = this.intX - v_obj.intX;
    this.intY = this.intY - v_obj.intY;
  } else {
    this.intX = this.intX - v_obj.x;
    this.intY = - this.intX - v_obj.y;
  }
  return this;
}

Point.prototype.getException = function () {
  return this.strException;
}

Point.prototype.getX = function () {
  return this.intX;
}
Point.prototype.setX = function ( intX ) {
  this.intX = intX;
}

Point.prototype.getY = function () {
  return this.intY;
}
Point.prototype.setY = function ( intY ) {
  this.intY = intY;
}

Point.prototype.offsetBy = function ( intDeltaX, intDeltaY  ) {
  this.intX += intDeltaX;
  this.intY += intDeltaY;
}

Point.prototype.x = Point.prototype.getX();
Point.prototype.y = Point.prototype.getY();

/******************************************************************************************
** Rectangle Object
*******************************************************************************************/
function Rectangle( intLeft, intTop, intRight, intBottom ) {
  this.intLeft = Math.min( intLeft, intRight );
  this.intTop = Math.min( intTop, intBottom );
  this.intRight = Math.max( intLeft, intRight );
  this.intBottom = Math.max( intTop, intBottom );
}

Rectangle.prototype.getBottom = function () {
  return this.intBottom;
}

Rectangle.prototype.getHeight = function () {
  return this.intBottom - this.intTop;
}

Rectangle.prototype.getLeft = function () {
  return this.intLeft;
}

Rectangle.prototype.getRight = function () {
  return this.intRight;
}

Rectangle.prototype.getTop = function () {
  return this.intTop;
}

Rectangle.prototype.getWidth = function () {
  return this.intRight - this.intLeft;
}

Rectangle.prototype.contains = function ( pnt ) {
  var intX = pnt.getX();
  var intY = pnt.getY();
  return ( this.intLeft <= intX && intX <= this.intRight &&
           this.intTop  <= intY && intY <= this.intBottom );
}

Rectangle.prototype.doesNotContain = function ( pnt ) {
  return !this.contains( pnt );
}



/******************************************************************************************
** Field Object
Widget = function ( strName, strCaption, strDatatype, strWidgetType, strWidgetSubtype, intMaxSize ) {
	this.strName = strName;
	this.strCaption = strCaption;
	this.strDatatype = strDatatype;
	this.strWidgetType = strWidgetType;
	this.strWidgetSubtype = strWidgetSubtype;
	this.intMaxSize = intMaxSize;
}

Widget.Subtypes = [ 'text', 'password', 'checkbox', 'hidden', 'radio', 'file', 'button', 'reset', 'submit', 'image' ];

Widget.prototype.createWidget = function ( objContainer, varValue ) {
	if ( objContainer ) {
		switch ( strWidgetType.toUpperCase() ) {
			case "FIELD":
				var objInitialiser = new Object();
				objInitialiser.id = "fld" + this.strName;
				objInitialiser.type = this.strWidgetSubtype;
				objInitialiser.value = varValue;
				if ( this.strWidgetType.toUpperCase() == 'FIELD' || this.strWidgetType.toUpperCase() == 'PASSWORD' ) {
				  if ( this.intMaxSize ) {
						objInitialiser.maxlength = this.intMaxSize;
					}
					objInitialiser.size = varValue.length;
				}
			  this.objLabel = dom.createElement( objContainer, "label", 
				                                   { 'id' : 'lbl' + this.strName, 'for' : 'fld' + this.strName }, 
																					 this.strCaption ); 
        this.objField = dom.createElement( objLabel, "input", objInitialiser );
				break;
		}
		return true;
	} else {
		return false;
	}
}
*******************************************************************************************/

function Exception( strIdentifier, strMessage ) {
  this.strIdentifier = strIdentifier;
  this.strMessage = strMessage;
}

Exception.prototype.description = function () {
  return this.strMessage;
}

Exception.prototype.identifier = function () {
  return this.strIdentifier;
}

Exception.prototype.message = function () {
  return this.strMessage;
}

Exception.prototype.name = function () {
  return this.strIdentifier;
}

Exception.prototype.number = function () {
  return this.strIdentifier;
}

function Constructor( objInstance, strMethod, objData ) {
  // Can be used to construct formatted content, on demand
  this.objInstance = objInstance;
  this.strMethod = strMethod;
  this.objData = objData;
  this.objException = null;
}

Constructor.prototype.failed = function () {
  return ( this.objException != null );
}

Constructor.prototype.generate = function () {
  if ( this.objInstance ) {
    try {
      return eval( "this.objInstance." + this.strMethod + "( this.objData )" );
    }
    catch ( e ) {
      this.objException = new Exception( e.name    ? e.name    : e.number, 
                                         e.message ? e.message : e.description );
    }
  }
}

Constructor.prototype.succeeded = function () {
  return ( this.objException == null );
}

/********************
** Date Extensions
**  .prototype
**   .clone
**   .addDays              ( intDays )
**   .addMonths            ( intMonths )
**   .addYears             ( intYears )
**   .differenceInDaysFrom ( dtmOtherDate )
**   .getEndOfMonth
**   .getRange             ( intDaysBack, intDaysForward )
**   .getStartOfMonth
**   .getYYYYMMDD
**  .normalise             ( varDate )
**  .stringToDate          ( strDate )
**  .stringToStartOfRange  ( strDate )
**  .stringToEndOfRange    ( strDate )
*********************/
Date.prototype.addDays = function( intDays ) {
	var intHourSize = 1000 * 60 * 60;
  var intDaySize = intHourSize * 24;
  var dtmNewDate = this.clone();
  dtmNewDate.setTime( this.getTime() + (intDaySize * intDays) );
	switch ( this.getTimezoneOffset() - dtmNewDate.getTimezoneOffset() ) {
		case 60:
		  dtmNewDate.setTime( dtmNewDate.getTime() - intHourSize );  // Subtract an hour, clocks going back!
		  break;
		case -60:
		  dtmNewDate.setTime( dtmNewDate.getTime() + intHourSize );  // Add an hour, clocks going forward!
		  break;
		default: 
		  // Should be zero, i.e. timezone should not have changed
			break;
	}
  return dtmNewDate;
}

Date.prototype.addMonths = function ( intMonths ) {
  var deltaYears  = 0;
  
  while ( intMonths > 12 ) {
    deltaYears++;
    intMonths -= 12;
  }
  while ( intMonths < -12 ) {
    deltaYears--;
    intMonths += 12;
  }
  
  var dtmNewDate = new Date( this.getFullYear() + deltaYears, this.getMonth() + intMonths, 1);
  if (this.getDate() > 28) {
    switch (dtmNewDate.getMonth()) {
      case 3:
      case 5:
      case 8:
      case 10: // 30 day months
        if (this.getDate() == 31) {
          dtmNewDate.setDate( 30 );
        } else {
          dtmNewDate.setDate( this.getDate() );
        }
        break;
      case 1: // 28 or 29 day month - always set to 28.
        dtmNewDate.setDate( 28 );
        break;
      default:  // 31 day months
        dtmNewDate.setDate( this.getDate() );
        break;
    }
  } else {
    dtmNewDate.setDate( this.getDate() );
  }  
  return dtmNewDate;
}

Date.prototype.addYears = function ( intYears ) {
  return (new Date( this.getFullYear() + intYears, this.getMonth(), this.getDate() ) );
}

Date.prototype.clone = function () {
  return new Date( this.getFullYear(), this.getMonth(), this.getDate(), 
                   this.getHours(), this.getMinutes(), this.getSeconds(), this.getMilliseconds() );
}
                           
Date.prototype.differenceInDaysFrom = function ( dtmOtherDate ) {
  var numMilliseconds = this.valueOf() - dtmOtherDate.valueOf();
  return (numMilliseconds / 3600000 / 24);
}

Date.prototype.getEndOfMonth = function () {
  var intLeapDays = ( ( Date.isLeapYear( (this.getFullYear()).toString() ) ) && ( this.getMonth() == 1 ) ) ? 1 : 0;
  var dtmNewDate = this.clone();
  dtmNewDate.setDate( Date.monthLengths[ dtmNewDate.getMonth() ] + intLeapDays );
  return dtmNewDate;
}

Date.prototype.getRange = function ( intDaysBack, intDaysForward ) {
  var dtmStart = this.addDays( intDaysBack );
  var dtmFinish = this.addDays( intDaysForward );
  var strRange = dtmStart.getYYYYMMDD() + "-" + dtmFinish.getYYYYMMDD();
  return strRange;
}

Date.prototype.getStartOfMonth = function() {
  var dtmNewDate = this.clone();
  dtmNewDate.setDate( 1 );
  return dtmNewDate;
}

Date.prototype.getYYYYMM = function () {
  var numYear = this.getFullYear();
  var numMonth = this.getMonth() + 1;
  return numYear.toString() + 
         (numMonth < 10 ?"0" :"") + numMonth.toString();
}

Date.prototype.getYYYYMMDD = function () {
  var numYear = this.getFullYear();
  var numMonth = this.getMonth() + 1;
  var numDate = this.getDate();
  return numYear.toString() + 
         (numMonth < 10 ?"0" :"") + numMonth.toString() +
         (numDate < 10 ?"0" :"") + numDate.toString();
}

Date.prototype.isLeapYear = function () {
  var intYear = this.getFullYear();
  return ((intYear % 4 == 0) && (intYear % 100 != 0)) || (intYear % 400 == 0);
}

Date.prototype.toISO8601 = function ( blnIncludeSeparators ) {
  var numYear = this.getFullYear();
  var numMonth = this.getMonth() + 1;
  var numDate = this.getDate();
  var strDateSeparator = ( blnIncludeSeparators ) ? "-" : "";
  
  var strISO8601 = numYear.toString() + strDateSeparator +
         (numMonth < 10 ?"0" :"") + numMonth.toString() + strDateSeparator +
         (numDate < 10 ?"0" :"") + numDate.toString();
  return strISO8601;
}

Date.getEndOfMonth = function ( varDate ) {
  var dtmDate;
  var intLeapYearDays;
  
  if ( Date.isYYYYMMDD( varDate ) ) {
    var intYear = parseInt(Date.getYYYY( varDate ), 10);
    var intMonth = parseInt(Date.getMM ( varDate ), 10) - 1;
    intLeapYearDays = ( ( Date.isLeapYear( varDate ) && ( intMonth == 1 ) ) ? 1 : 0 );
    var intDate = (Date.monthLengths[ intMonth ]) + intLeapYearDays;
    dtmDate = new Date( intYear, intMonth, intDate );
  } else if ( varDate.constructor == Date ) {
    intLeapYearDays = ( ( varDate.isLeapYear() && ( varDate.getMonth() == 1 ) ) ? 1 : 0 );
    dtmDate = varDate
    dtmDate.setDate( Date.monthLengths[ varDate.getMonth() ] + intLeapYearDays );
  } else {
    throw new Error( "Date.getEndOfMonth needs either a Date or at least the Year and Month fragment of a YYYYMMDD string" );
  }
  return dtmDate;
}

Date.getStartOfMonth = function ( varDate ) {
	var dtmDate;
	
	if ( Date.isYYYYMMDD( varDate ) ) {
    dtmDate = new Date( intYear, intMonth, 1 );
    var intMonth = parseInt(Date.getMM ( varDate ), 10) - 1;
    var intYear = parseInt(Date.getYYYY( varDate ), 10);
  } else if ( varDate.constructor == Date ) {
    dtmDate = varDate
    dtmDate.setDate( 1 );
  } else {
    throw new Error( "Date.getStartOfMonth needs either a Date or at least the Year and Month fragment of a YYYYMMDD string" );
  }
  return dtmDate;
}

Date.getMM = function ( strDate ) {
  var strMM = strDate.substring( 4, 6 );
  return strMM;
}

Date.getYYYY = function ( strDate ) {
  return strDate.substring( 0, 4 );
}

Date.millisecondsToDuration = function( v_intMilliseconds ) {
  var numHours = parseInt( v_intMilliseconds / 360000 );
  var numMinutes = parseInt( ( v_intMilliseconds % 360000 ) / 60000 ); 
  var numSeconds = parseInt( ( ( v_intMilliseconds % 360000 ) % 60000 ) / 1000 );
  return numHours + ":" + numMinutes.toPaddedString( 2, "0", "LEFT" ) + ":" + numSeconds;
}

Date.isLeapYear = function ( strDate ) {
  var intYear = new Number( Date.getYYYY( strDate ) );
  return ((intYear % 4 == 0)  && (intYear % 100 != 0)) || (intYear % 400 == 0); 
}

Date.isRange = function ( varDate ) {
  return (varDate.constructor == String && varDate.length >= 17 && varDate.substring( 9, 10 ) == "-" );
}

Date.isYYYYMMDD = function ( varDate ) {
  return (varDate.constructor == String && varDate.length >= 6);
}

Date.isISO8601Date = Date.isYYYYMMDD;

Date.monthLengths = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];

Date.normalise = function ( varDate ) {
  var dtmDate;
  if (varDate) {
    switch (varDate.constructor) {
      case String:
        dtmDate = this.stringToDate( varDate );
        break;
      case Date:
        dtmDate = varDate.clone();
        break;
      default:
        dtmDate = new Date();
        break;
    }
  } else {
    dtmDate = new Date();
  }
  return dtmDate;
}

Date.stringToDate = function ( strDate ) {
  // converts string in yyyymmdd format to a date
  var dtmDate = new Date( 1996, 0, 31 ); // Use a leap year to avoid any day/month mismatches in partial dates!
  dtmDate.setDate( parseInt( strDate.substring( 6, 8 ), 10 ) );
  dtmDate.setMonth( parseInt( strDate.substring( 4, 6 ), 10 ) - 1 );
  dtmDate.setFullYear( parseInt( strDate.substring( 0, 4 ), 10 ) );
  return dtmDate;
}

Date.stringToStartOfRange = function ( strDate ) {
  return Date.stringToDate( strDate );
}

Date.stringToEndOfRange = function ( strDate ) {
  // converts second date string in yyyymmdd-yyyymmdd format to a date
  if (strDate) {
    if (strDate.constructor == String) {
      if (strDate.length >= 17) {
        var dtmDate = new Date( 1996, 0, 31 );  // Use a leap year to avoid any day/month mismatches in partial dates!
			  dtmDate.setDate( strDate.substring( 15, 17 ) );
			  dtmDate.setMonth( strDate.substring( 13, 15 ) - 1 );
			  dtmDate.setFullYear( strDate.substring( 9, 13 ) );
 				return dtmDate;
  		}
    }
  }
}

/********************
** Math Extensions
*********************/

Math.isEven = function( num ) {
  return ( ( num % 2 ) == 0 );
}

Math.isOdd = function( num ) {
  return !Math.isEven( num );
}

/********************
** Number Extensions
*********************/

Number.prototype.toPaddedString = function ( intPadSize, strPad, strPadWhere ) {
	var blnTerminate = false;
	if ( (this.toString()).length >= intPadSize ) {
		return this;
	} else {
		if ( !strPad )      { strPad      = "0";    }  // Default to zero-padding
		if ( !strPadWhere ) { strPadWhere = "LEFT"; }  // on left of number.
		var numPadded = this;
		while ( (numPadded.toString()).length < intPadSize && !blnTerminate ) {
			if ( strPadWhere.toUpperCase() == "LEFT" ) {
				numPadded = (strPad + numPadded.toString()).valueOf();
			} else if ( strPadWhere.toUpperCase() == "RIGHT" ) {
				numPadded = (numPadded.toString() + strPad).valueOf();
			} else {
				// Invalid pad option, so we leave supplied number unchanged
				blnTerminate = true;
			}
		}
		return numPadded;
	}
}

/********************
** String Extensions
*********************/

String.space = function ( intCount ) {
  var strNew = "";
  
  for ( ; intCount > 0; intCount-- ) {
    strNew += " ";
  }
  return strNew;
}

String.prototype.TokenSeparators = /\s/;
String.prototype.TokenCharacters = /[A-Za-z0-9_]/;

String.prototype.isMemberOf = function isMemberOf( v_strSet, v_strSeparator ) {
  if ( typeof( v_strSet ) == "undefined" || typeof( v_strSeparator ) == "undefined" ) {
    return false;
  } else {
    var arrSet = v_strSet.split( v_strSeparator );
    if ( arrSet.length > 0 ) {
      for ( i = 0; i< arrSet.length; i++ ) {
        if ( this == arrSet[ i ] ) {
          return true;
        }
      }
    } else {
      return false;
    }
  }
}

String.prototype.isOneOf = function( v_arrOptions ) {
  var blnResult = false;
  for ( var intIndex = 0; intIndex < v_arrOptions.length; intIndex++ ){
    if ( this == v_arrOptions[ intIndex ] ) {
      blnResult = true;
      break;
    }
  }
  return blnResult;
}

String.prototype.ltrim = function() {
  return this.replace(/^\s*/, '' );  
}

String.prototype.parse = function ( strType, intMaxLength ) {
  var arrResult = [ "", this ];
  switch ( strType ) {
    case "Boolean":
      var rxp = new RegExp( "^(true|false)", "i" );
      if ( rxp.test( this ) ) {
        arrResult[ 0 ] = RegExp.$1;
        arrResult[ 1 ] = RegExp.rightContext;
      }
      break;
      
    case "Date":  // ISO 6801 Date
      // YYYY[-]MM[-]DD[ ]hh[:]mm[:]ss[.ssss] 
      // NB. We permit more than one space between date and time fields. This might be wrong!
      // [0] == $1 ==> YYYY, [1] == $2 ==> MM, [2] == $3 ==> DD
      // [3] == $4 ==> hh,   [4] == $5 ==> mm, [5] == $6 ==> ss.ssss 
      // (We have not restricted how many decimal places seconds may have.
      var rxp = new RegExp( "^(\d{4})-?(\d{2})-?(\d{2})\s*(?:(\d{2}):?(\d{2}):?(\d{2}(?:\.(\d+))?)?" );
      var arrExpressions = rxp.exec( this );
      if ( arrExpressions.length == 3 ) { // Date part only
        arrResult[ 0 ] = arrExpressions.join( "" );
      } else {                        // Date and Time parts
        arrResult[ 0 ] = arrExpressions[ 0 ] + arrExpressions[ 1 ] + arrExpressions[ 2 ] + " " +
                         arrExpressions[ 3 ] + arrExpressions[ 4 ] + arrExpressions[ 5 ];
      }
      arrResult[ 1 ] = RegExp.rightContext;
      break;
      
    case "DateRange":
      break;
    
    case "Decimal":
      break;
    
    case "Integer":
      break;
    
    case "Float":
      break;
    
    case "Number":
      break;
    
    case "Token":
      var rxp = new RegExp( "^(/w)" );
      break;
    
    case "Whitespace":
      var rxp = new RegExp( "^(\s+)" );
      if ( rxp.test( this ) ) {
        arrResult[ 0 ] = RegExp.$1;
        arrResult[ 1 ] = RegExp.rightContext;
      }
      break;
    
    default:
      break;
  }
  return arrResult;
}

String.prototype.replaceAll = function( v_arrReplacements ) {
  var strTarget = this;
  for ( var intIndex = 0; intIndex < v_arrReplacements.length; intIndex++ ) {
    var arrReplacement = v_arrReplacements[ intIndex ];
    if ( arrReplacement.length == 2 ) {
      strTarget = strTarget.replace( arrReplacement[ 0 ], arrReplacement[ 1 ] );
    }
  }
  return strTarget;
}

String.prototype.rtrim = function() {
  return this.replace(/\s*$/, '' );  
}

String.prototype.toISO8601Date = function ( ) {
  return this.parse( "Date" );
}

String.prototype.trim = function() {
  return this.replace( /^\s+|\s+$/g, '' );
}

/********************
** Event Extensions
*********************/

//if ( !Event ) {
  function Event () {
    copyAttributes( this, window.event );
  }
//}

Event.prototype.getX = function ( ) {
  var intX = 0;
  if ( this.pageX ) {
    intX = this.pageX;
  } else if ( this.clientX ) {
    intX = this.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
  }
  return intX;
}

Event.prototype.getY = function ( ) {
  var intY = 0;
  if ( this.pageY ) {
    intY = this.pageY;
  } else if ( this.clientY ) {
    intY = this.clientY + document.body.scrollTop + document.documentElement.scrollTop;
  }
  return intY;
}

Event.prototype.getPosition = function ( ) {
  var objPosition = new Object();
  objPosition[ 'x' ] = this.getX( );
  objPosition[ 'y' ] = this.getY( );
  return objPosition;
}

Event.prototype.getTargetMovedFrom = function ( ) {
  return this.relatedTarget || this.fromElement;
}

Event.prototype.getTargetMovedTo = function ( ) {
  return this.relatedTarget || this.toElement;
}

Event.prototype.getTarget = function ( objThis ) {
	var objTarget = objThis;
  if ( this.target ) {
    objTarget = this.target;
  }
	else if (this.srcElement) {
		objTarget = this.srcElement;
	}
  if ( objTarget.nodeType == 3 ) {    // If target is a text node
    objTarget = objTarget.parentNode; // then return the parent
  }
  return objTarget;
}

Event.prototype.getButton = function ( ) {
  // TODO: getButton needs a complete rewrite
  var intButton = -1;
  if ( this.which ) {
    switch ( evt.which ) {
      case 0: return false;
      case 1: return "LEFT";
      case 2: return "MIDDLE";
      case 3: return "LEFT,MIDDLE";
      case 4: return "RIGHT";
      case 5: return "LEFT,RIGHT";
      case 6: return "MIDDLE,RIGHT";
      case 7: return "LEFT,MIDDLE,RIGHT";
    }
  } else if ( this.button ) {
    switch ( this.button ) {
      case 0: return "LEFT";
    }
  }
}

/********************
** HTMLTag Class
*********************/

HTMLTag = function ( strTagName, objAttributes, strContent ) {
  this.element = document.createElement( strTagName );
  this.children = [];
  if (objAttributes) {
    for ( strAttribute in objAttributes ) {
      this.element.setAttribute( strAttribute, objAttributes[ strAttribute ] );
    }
  }
  if (strContent) {
    dom.setInnerHTML( this.element, strContent );
  }
}

HTMLTag.prototype.addChild = function ( objTag ) {
  this.element.childNodes[ this.element.childNodes.length ] = objTag.getElement();
  this.children[ this.children.length ] = objTag;
}

HTMLTag.prototype.getElement = function () {
  return this.element;
}

HTMLTag.prototype.setAttribute = function ( strAttribute, strValue ) {
  var objElement = this.element;
  objElement.setAttribute( strAttribute, strValue );
}

HTMLTag.prototype.setContent = function ( strContent ) {
  var objElement = this.element;
  dom.setInnerHTML( objElement, strContent );
}


/******************************************************************************************
** DOM Extensions
** All DOM calls should be routed via an instance of this object. 
** We use an instance in case any state needs to be retained, although none is, at present.
** Compatibility issues are to be resolved HERE!
**
**  .prototype
**   .apply               ( arrValues, funApplier )
**   .createElement       ( objParent, strTag, objAttributes, varContent )
**   .getElementById      ( strId )
**   .registerListener    ( objElement, strEvent, funListener, blnUseCapture )
**   .deregisterListener  ( objElement, strEvent, funListener, blnUseCapture )
**   .getTagName          ( objElement )
**   .getUniqueId
**   .setVisibility       ( strId, blnVisible )
**
** NB. IE Does not support
**  .innerHTML
**  .className
**  .getAttribute
**  .setAttribute
**  .removeAttribute
**
*******************************************************************************************/
DOM = function () {
  this.Version = "0.1.9";
  this.Purpose = 'Provides a DOM abstraction layer for compatibility with all user agents';
  /** 
   **
  this.document;
  this.window;
    this.window.navigator
  **/
 this.objEvents = new Object();
 this.used = new Object();
}

DOM.prototype.classes = { 'button' : 'Southstar-Button',
                          'column' : 'Southstar-Column',
                          'combo'  : 'Southstar-Combo',
                          'label'  : 'Southstar-Label',
                          'field'  : 'Southstar-Field',
													'form'   : 'Southstar-Form',
													'row'    : 'Southstar-Row',
													'table'  : 'Southstar-Table'
												};
												
DOM.prototype.strings = { 'cancel' : 'Cancel',
                          'delete' : 'Delete',
                          'edit'   : 'Edit',
													'new'    : 'New',
													'ok'     : 'OK',
													'save'   : 'Save'
												};


DOM.prototype.appendContent = function ( varElement, varContent, objAttributes ) {
  var objElement = this.getObject( varElement );
  
  if ( objElement && objElement.objClientArea ) {
    objElement = objElement.objClientArea;        //Check for content and a client area in which to put it!
  }
  if ( varContent && objElement ) {
    if ( objElement.tagName.toLowerCase().isOneOf( [ "select", "optgroup" ] ) ) {
      // handle SELECT|OPTGROUP --> OPTION content
      switch ( varContent.constructor ) {
        case Array:
          for ( var intIndex = 0; intIndex = varContent.length; intIndex++ ) {
            dom.createElement( objElement, "OPTION", objAttributes, varContent[ intIndex ] );
          }
          break;
        case Object:
          var varOptions = varContent.options;
          var varBranch = varContent.branch;
          var objIgnore = varContent.ignore;
          var blnShowHide = varContent.showhide;
          if ( varOptions.constructor == Array ) {
            for ( var intIndex = 0; intIndex < varOptions.length; intIndex++ ) {
              var strOption = varOptions[ intIndex ];
              if ( ! (objIgnore && objIgnore[ strOption ] ) ) {
                dom.createElement( objElement, "OPTION", objAttributes, strOption );
              }
            }
          } else if ( varOptions.constructor == Object ) { // Also applicable to tree
            for ( var strOption in varOptions ) {
              if ( ! (objIgnore && objIgnore[ strOption ] ) ) {
                var objOption = varOptions[ strOption ];
                if ( varBranch && objOption[ varBranch ] ) {
                  var objGroupAttributes = { 'label'       : strOption,
                                              'visibility' : true };
                  if ( blnShowHide ) objGroupAttributes.click = this.onShowHide;
                  var objOptGroup = dom.createElement( objElement, "OPTGROUP", objGroupAttributes, false );
                  this.appendContent( objOptGroup, 
                                      { 'options'    : objOption[ varBranch ], 
                                        'branch'     : varBranch,
                                        'showhide'   : blnShowHide }, 
                                      objAttributes );
                } else {
                  dom.createElement( objElement, "OPTION", objAttributes, strOption );
                }
              }
            }
          }
          break;
      }
    } else {
      /* Handle possibly undefined constructor functions, below */
     if ( typeof(Form) == "undefined" ) var Form = null;
      switch ( varContent.constructor ) {
        case Array:   // Function call with arguments
          if ( varContent.length >= 1 && varContent[0].constructor == Function ) {
            var fnContent = varContent.shift();
            var strArguments = varContent.join();
            eval( "fnContent( " + strArguments + " )" );
          } else {
            for ( var intIndex = 0; intIndex < varContent.length; intIndex++ ) {
              this.appendContent( objElement, varContent[ intIndex ], objAttributes );
              if ( objAttributes && objAttributes.HORIZONTALRULE ) dom.createElement( objElement, "hr" );
            }
          }
          break;
        case Date:
          objElement.innerHTML += varContent.toLocaleString();
        case Number:
          objElement.innerHTML += varContent.toString();
          break;
        case String:  // Simple content
          objElement.innerHTML += varContent;
          //dom.createElement( objElement, "div", objAttributes, varContent );
          break;
        case Form:     // A Form
          varContent.parentWindow = objElement;
          varContent.show( objElement );
          break;
        case Function: // Content producing function
          varContent( objElement.getRootElement() );
          break;
        case Object:
          if ( varContent.URL ) {
            
          } else {
            for ( var strAttribute in varContent ) {
              this.appendContent( objElement, varContent[ strAttribute ], { 'class' : strAttribute } );
            }
          }
          break;
        default:
          objElement.innerHTML += varContent;
          break;
      }
    }
  }

}

DOM.prototype.apply = function ( arrValues, funApplier ) {
  var arrNew = [];
  for ( var v in arrValues ) {
    arrNew[ arrNew.length ] = funApplier( v );
  }
}

/********************
** Array Extensions
**   .hasMember            ( varValue )
**   .hasMemberIgnoreCase  ( varValue )
*********************/
DOM.prototype.arrayHasMember = function( arrArray, varValue ) {
  for ( i in arrArray ) {
    if ( arrArray[i] == varValue ) return true;
  }
  return false;
}

DOM.prototype.arrayHasMemberIgnoreCase = function( arrArray, varValue ) {
  for ( i in arrArray ) {
    if ( arrArray[i].toString().toUpperCase() == varValue.toString().toUpperCase() ) return true;
  }
  return false;
}

DOM.prototype.buildURL = function ( objElements ) { 
  // Reference RFC 3986
  var strURL = "";
  if ( objElements ) {
    if ( objElements.scheme )  strURL += objElements.scheme + ":"; else strURL += window.location.protocol;
    if ( objElements.username) strURL += objElements.username + "@";
	  if ( objElements.host )    strURL += '//' + objElements.host;
    if ( objElements.port )    strURL += ":" + objElements.port;
    if ( objElements.path )    strURL += objElements.path 
                                      + ((objElements.path.charAt( objElements.path.length ) == '/' ) ? '' : '/');
		if (objElements.filename)  strURL += objElements.filename;
    if (objElements.query)     strURL += "?" + objElements.query;
    if (objElements.fragment)  strURL += "#" + objElements.fragment;
  }
  return strURL;
}

DOM.prototype.buildURI = function( objElements ) {
  // Uses names adopted in Javascript DOM
  var strURI = ( objElements.protocol ) ? objElements.protocol + "//"   : window.location.protocol
             + ( objElements.username ) ? objElements.username + "@" : ""
             + ( objElements.host ) ? "//" + objElements.host    : "//" + window.location.host
             + ( objElements.port ) ? ":" + objElements.port     : window.location.port
             + ( objElements.pathname ) ? "/" + objElements.pathname     
                                                   + ((objElements.pathname.charAt( objElements.pathname.length ) == '/' ) ? '' : '/')
                                                                        : window.location.pathname
             + ( objElements.search ) ? "?" + objElements.search   : ""
             + ( objElements.hash ) ? "#" + objElements.hash     : "";
  return strURI;
}

DOM.prototype.clearElement = function ( obj ) {
	if ( obj ) {
		var objNew = obj.cloneNode(false);          // Clone original, without content
	  obj.parentNode.insertBefore( objNew, obj ); // Prepend clone before original
	  obj.parentNode.removeChild( obj );          // Remove original
	  return objNew;
	} else {
		return obj;
	}
}

DOM.prototype.createButton = function ( objParent, strLabel, objAttributes, objMembers, strImagePath, strImageWidth, strImageHeight, objListeners ) {
  var objTag;
  if ( objParent ) {
    objTag = dom.createElement( objParent, "button", objAttributes, strLabel ); 
    if (objMembers) {
      for ( var strMember in objMembers ) {
        eval( "objTag." + strMember + " = objMembers[ strMember ]" );
      }
    }
    if (strImagePath && (strImagePath != "")) {
      dom.createElement( objTag, "img", { 'src' : strImagePath, 'width': strImageWidth, 'height': strImageHeight });
    }
    if (objListeners) {
  	  for ( var strEvent in objListeners ) {
  	    dom.registerListener( objTag, strEvent, objListeners[ strEvent ], false );
  	  }
  	}
  }
  return objTag;
}


DOM.prototype.createElement = function ( objParent, strTag, objAttributes, varContent ) {
  var obj = document.createElement( strTag );
  if (objAttributes) {
	  for ( var strAttribute in objAttributes ) {
      var varAttribute = objAttributes[ strAttribute ];
      if ( typeof( varAttribute ) != "undefined" ) {
        switch ( varAttribute.constructor ) {
          case Number:
          case String:
            switch (strAttribute.toLowerCase()) { // Handle known IE bugs
              case "class":
                obj.className = objAttributes[strAttribute];
                break;
              case "colspan":
                obj.colSpan = objAttributes[strAttribute];
                break;
              case "matcher":
                obj.matcher = objAttributes[strAttribute];
                break;
              case "onclick":
                obj.onclick = objAttributes[strAttribute];
                break;
              case "style":
                obj.style = objAttributes[strAttribute];
                break;
              default:
                //obj.setAttribute(strAttribute, objAttributes[strAttribute]); /* This appears not to work. Try alternative! */
                eval( "obj[ '" + strAttribute + "'] = varAttribute;" );
                break;
            }
            break;
          
          case Function:
            strAttribute = strAttribute.replace( /^on/, "" );
            this.registerListener( obj, strAttribute, varAttribute, false );
            break;
            
          default:
            if ( !( strTag == "SELECT" && strAttribute == "options" ) ) {
              eval( "obj['" + strAttribute + "'] = varAttribute;" );
            }
            break;
        }
      }
	  }
	}
  /*
  if (varContent) {
    switch (varContent.constructor) {
      case String: 
        obj.innerHTML = varContent;
        break;
			case Object:
			  for ( var strAttribute in varContent ) {
          eval( "obj." + strAttribute + " = varContent[ strAttribute ]" );
				}
				break;
      default:
        obj.appendChild( varContent );
        break;
    }
  }
  */
  this.appendContent( obj, varContent );
  
  if (objParent) {
    var objBefore = false;
    if (objParent.constructor == String) {
      objParent = dom.getElementById( objParent );
    } else if ( objParent.container || objParent.before || objParent.after ) {
      if ( objParent.before )    objBefore = dom.getObject( objParent.before );
      if ( objParent.container ) objParent = dom.getObject( objParent.container );
      else objParent = false;  // We *must* specify container if we supply before or after
    }
    if (objParent) {
      if ( objBefore ) {
        objParent.insertBefore( obj, objBefore );
      } else {
        objParent.appendChild( obj );
      }
    }
  }
  return obj;
}

DOM.prototype.createCaptionedCombo = function ( objParent, objRange, strName, strCaption, varValue ) {
  var objLabel;
  var objSelect;
	var objDiv;
  
  if ( objParent ) {
    if ( strCaption ) {
      objLabel = dom.createElement( objParent, "label", 
			                              { 'id' : 'lbl' + strName, 'class' : this.classes.label , 'for' : 'cmb' + strName }, strCaption ); 
      objSelect = dom.createElement( objLabel, "select", 
			                               { 'id' : 'cmb' + strName, 'class' : this.classes.field, 'name' : 'cmb' + strName, 'value' : varValue } );
    } else {
      objSelect = dom.createElement( objParent, "select", 
			                               { 'id' : 'cmb' + strName, 'class' : this.classes.field, 'name' : 'cmb' + strName, 'value' : varValue } );
    }
		if ( objRange && objSelect ) {
			for ( var strKey in objRange ) {
				var objItem = dom.createElement( objSelect, "option", { 'value' : objRange[ strKey ] }, strKey );
			}
		}
		dom.createElement( objParent, "br" );
    return objSelect;
  } else {
    return false;
  }
}

DOM.prototype.createCombo = function ( objParent, objRange, strName, strClass, varValue, blnReadOnly, blnBreak, objIgnore ) {
  var objSelect;
  
  if ( objParent ) {
		if ( blnReadOnly ) {
      objSelect = dom.createElement( objParent, "select", 
			            { 'id' : 'cmb' + strName, 'class' : strClass, 'name' : 'cmb' + strName, 'value' : varValue, 'disabled' : true } );
		} else {
      objSelect = dom.createElement( objParent, "select", 
			            { 'id' : 'cmb' + strName, 'class' : strClass, 'name' : 'cmb' + strName, 'value' : varValue } );
		}
		if ( objRange && objSelect ) {
      if ( typeof( objRange ) == Object ) {
  			for ( var strKey in objRange ) {
          if ( !( objIgnore && objIgnore[ strKey ] ) ) {
     				var objItem = dom.createElement( objSelect, "option", { 'value' : objRange[ strKey ] }, strKey );
          }
  			}
      } else if ( typeof( objRange == Array ) ) {
        for ( var intIndex = 0; intIndex < objRange.length; intIndex++ ) {
          if ( !( objIgnore && objIgnore[ strKey ] ) ) {
            var objItem = dom.createElement( objSelect, "option", { 'value' : objRange[ intIndex ] }, objRange[ intIndex ] );
          }
        }
      }
		}
		if ( blnBreak ) {
			dom.createElement( objParent, "br" );
		}
    return objSelect;
  } else {
    return false;
  }
}

DOM.FIELDTYPE = {
  'text'     : 'text',
  'password' : 'password',
  'checkbox' : 'checkbox',
  'hidden'   : 'hidden',
  'radio'    : 'radio',
  'file'     : 'file',
  'button'   : 'button',
  'reset'    : 'reset',
  'submit'   : 'submit',
  'image'    : 'image'
}

DOM.prototype.createCaptionedField = function ( objParent, strType, strName, strCaption, varValue, blnReadOnly, v_objAttributes ) {
  var objLabel;
  var objField;
  
  if ( objParent ) {
		var objAttributes = new Object();
		objAttributes.id = 'fld' + strName;
		objAttributes.name = objAttributes.id;
		objAttributes.type = strType;
		objAttributes.value = varValue;
		objAttributes[ 'class' ] = dom.classes.field;
		if ( blnReadOnly ) {
			objAttributes.disabled = true;
		}
    if ( v_objAttributes ) objAttributes = dom.objectMerge( objAttributes, v_objAttributes );
    if ( strCaption ) {
			objLabel = dom.createElement( objParent, "span", { 'class' : this.classes.label }, strCaption );
    }
    objField = dom.createElement( objParent, "input", objAttributes );
		dom.createElement( objParent, "br" );
    return objField;
  } else {
    return false;
  }
}

DOM.prototype.createField = function ( objParent, strType, strName, strClass, varValue, blnReadOnly, blnBreak ) {
  var objField;
  
  if ( objParent ) {
		var objAttributes = new Object();
		objAttributes.id = 'fld' + strName;
		objAttributes.name = objAttributes.id;
		objAttributes.type = strType;
		objAttributes.value = varValue;
		objAttributes[ 'class'] = strClass;
		if ( blnReadOnly ) {
			objAttributes.disabled = true;
		}
    objField = dom.createElement( objParent, "input", objAttributes );
		if ( blnBreak ) {
			dom.createElement( objParent, "br" );
		}
    return objField;
  } else {
    return false;
  }
}

DOM.prototype.createForm = function ( objParent, objAttributes, varContent ) {
	var objFormAttributes = clone( objAttributes );
	objFormAttributes[ 'class' ] = this.classes.form;
  return dom.createElement( objParent, "form", objFormAttributes, varContent );
}

DOM.prototype.createAttributes = function ( /* variable arguments */ ) {
  // Processes pairs of arguments into an object. Ignores singletons and items with undefined value
  // Warning: Absence of a item with a boolean value *must* be treated as False, 
  // since we won't add 'item' : false here!
  var objReturn = new Object();
  for ( var intIndex = 0; intIndex < arguments.length; intIndex += 2 ) {
    if ( arguments[ intIndex ] && arguments[ intIndex + 1 ] ) {
      objReturn[ arguments[ intIndex ] ] = arguments[ intIndex + 1 ];
    }
  }
  return objReturn;
}

DOM.prototype.createTOC = function( v_objParent, v_strHeaders ) {
  return; // For now, this does not work, so exit!
  var objParent = this.getObject( v_objParent );
  var arrHeaders = v_strHeaders.split( "," );
  if ( objParent && arrHeaders && arrHeaders.length >= 1 ) {
    var objRoot = this.createElement( objParent, "UL", { 'class' : 'toc' }, false );
    var objLevel = objRoot;
    var arrLevels = new Array();
    for( var intIndex = 0; intIndex <= arrHeaders.length; intIndex++ ) {
      var arrElements = document.getElementsByTagName( arrHeaders[ intIndex ] );
      arrLevels[ intIndex] = new Array();
      if ( arrElements && arrElements.length > 0 ) {
        var strTag = arrHeaders[ intIndex ];
        for ( var intElementIndex = 0; intElementIndex < arrElements.length; intElementIndex++ ) {
          var objElement = this.createElement( objLevel, "LI", { 'class' : 'tocentry ' + strTag }, false );
          arrLevels[ intIndex] [ intElementIndex ] = objElement;
        }
      }
    }
  }
}

DOM.prototype.eraseChildren = function ( obj ) {
  if ( obj ) {
    while ( obj.lastChild ) {
      obj.removeChild( obj.lastChild );
    }
  }
}

/***********************************************************************************
 * Class Manipulation
 ***********************************************************************************/
DOM.prototype.classRename = function classRename( r_objElement, v_strOldClass, v_strNewClass ) {
  var strClasses = r_objElement.className;
  if ( v_strOldClass.isMemberOf( strClasses, " " ) ) {
    var arrClasses = strClasses.split( " " );
    for ( var i = 0; i < arrClasses.length; i++ ) {
      var strClass = arrClasses[ i ];
      if ( strClass == v_strOldClass ) {
        arrClasses[ i ] = v_strNewClass;
      }
    }
    r_objElement.className = arrClasses.join( " " );
  }
}

/***********************************************************************************
 * Style sheet manipulation
 * objStyle is an anonymouse object with named properties corresponding to elements
 ***********************************************************************************/

DOM.prototype.buildCSSRule = function ( strRuleSelector, objStyle ) {
  var strRule = strRuleSelector + " {";
  if ( objStyle ) {
    for ( var strAttribute in objStyle ) {
      strRule += " " + strAttribute + ":" + objStyle[ strAttribute ] + ";";
    }
  }
  strRule += " }";
  return strRule;
}

DOM.prototype.deleteCSSRule = function ( objSheet, intRule ) {
  var blnSuccess = true;
  if ( objSheet ) {
    if ( objSheet.deleteRule ) {
      objSheet.deleteRule ( intRule );
    } else if ( objSheet.removeRule ) {
      objSheet.removeRule( intRule );
    } else {
      blnSuccess = false;
    }
  }
  return blnSuccess;
}

DOM.prototype.findCSSRuleReference = function ( strRuleSelector ) {
  var objRule = false;
  if ( document.styleSheets ) {
    for ( var intSheet = 0; intSheet < document.styleSheets.length  && !objRule; intSheet++ ) {
      var objSheet = document.styleSheets[ intSheet ];
      var arrRules = objSheet.cssRules? objSheet.cssRules: objSheet.rules;
      for ( intRule = 0; intRule < arrRules.length && !objRule; intRule++ ) {
        if ( arrRules[ intRule ].selectorText.toLowerCase() == strRuleSelector ) {
          objRule = { 'sheet' : intSheet, 'rule' : intRule };
        }
      }
    }
  }
  return objRule;
}

DOM.prototype.getCSSRules = function( v_objSheet ) {
  if ( v_objSheet.cssRules ) {
    return v_objSheet.cssRules;
  } else if ( v_objSheet.rules ) {
    return v_objSheet.rules;
  } else if ( v_objSheet.Rules ) {
    return v_objSheet.Rules;
  } else {
    return false;
  }
}

DOM.prototype.insertCSSRule = function ( objSheet, strRuleSelector, objStyle, intIndex ) {
  var blnSuccess = true;
  if ( objSheet ) {
    if ( objSheet.insertRule ) {
      var strCSSRule = this.buildCSSRule( strRuleSelector, objStyle );
      objSheet.insertRule( strCSSRule, intIndex );
    } else if ( objSheet.addRule ) {
      var strDeclaration = "";
      for ( var strAttribute in objStyle ) {
        strDeclaration += " " + strAttribute + " : " + objStyle[ strAttribute ] + ";";
      }
      objSheet.addRule( strRuleSelector, strDeclaration, intIndex );
    } else {
      blnSuccess = false;
    }
  } else {
    blnSuccess = false;
  }
  return blnSuccess;
}

DOM.prototype.replaceCSSRule = function ( strRuleSelector, objStyle ) {
  // Expects an object { attribute1 : value1, attribute2 : value2, ... }
  var blnSuccess = false;
  var objRuleReference = this.findCSSRule( strRuleSelector ); 
  if ( objRuleReference ) {
    var objSheet = document.styleSheets[ objRuleReference.sheet ];
    var arrRules = objSheet.cssRules? objSheet.cssRules: objSheet.rules;
    var intNewIndex = objRuleReference.rule - 1;
    this.deleteCSSRule( objSheet, objRuleReference.rule );
    this.insertCSSRule( objSheet, strRuleSelector, objStyle, intNewIndex );
    blnSuccess = true;
  }
  return blnSuccess;
}

DOM.prototype.updateCSSRule = function (  strRuleSelector, objStyle ) {
  // Expects an object { attribute1 : value1, attribute2 : value2, ... }
  var blnSuccess = false;
  var objRuleReference = this.findCSSRule( strRuleSelector );
  if ( objRuleReference ) {
    var objSheet = document.styleSheets[ objRuleReference.sheet ];
    var arrRules = objSheet.cssRules? objSheet.cssRules: objSheet.rules;
    var objRule = arrRules[ objRuleReference.rule ];
    
    for ( strAttribute in objStyle ) {
      objRule.style[ strAttribute ] = objStyle[ strAttribute ];
    }
    blnSuccess = true;
  }
  return blnSuccess;
}

/*
 * Cookie Processing
 */
DOM.prototype.loadCookies = function( ) {
  var arrCookies = document.cookie.split( ";" );
  this.cookies = new Object();
  for ( var intIndex = 0; intIndex < arrCookies.length; intIndex++ ) {
    var arrElements = arrCookies[ intIndex ].split( "=" );
    if ( arrElements.length == 2 ) {
      this.cookies[ arrElements[ 0 ].trim() ] = arrElements[ 1 ];
    }
  }
}

DOM.prototype.deleteCookie = function( v_strCookie ) {
  document.cookie = v_strCookie + '=; expires=Thu, 01-Jan-70 00:00:01 GMT;';
  delete this.cookies[ v_strCookie ];
}

DOM.prototype.readCookie = function( v_strCookie ) {
  var arrCookies = document.cookie.split( ";" );
  var strSearch = v_strCookie + "=";
  for ( var intIndex = 0; intIndex < arrCookies.length; intIndex++ ) {
    var strCookie = arrCookies[ intIndex ].ltrim();
    if ( strCookie.indexOf( strSearch ) == 0 ) {
      return strCookie.substring( strSearch.length );
    }
  }
  return null;
}

DOM.prototype.writeCookie = function( v_strCookie, v_strValue, v_intExpiryDays ) {
  if ( v_intExpiryDays ) {
    var dtmExpiry = new Date();
    dtmExpiry.setTime( dtmExpiry.getTime() + ( v_intExpiryDays * 86400000 ) );
    document.cookie = v_strCookie + "=" + v_strValue + "; expires=" + dtmExpiry.toGMTString() + "; path=/";
  } else {
    document.cookie = v_strCookie + "=" + v_strValue + "; path=/";
  }
  if ( this.cookies[ v_strCookie ] ) {
    this.cookies[ v_strCookie ] = v_strValue;
  }
}

DOM.prototype.getBody = function ( ) {
  if ( document.body ) {
    return document.body;
  } else {
    return document.childNodes[1].childNodes[1];
  }
}

DOM.prototype.getElementAttribute = function( v_varElement, v_strAttribute ) {
  var objElement = this.getObject( v_VarElement );
  var varResult = false;
  if ( objElement ) {
    eval( "varResult = objElement." + v_strAttribute + ";" );
  }
  return varResult;
}

DOM.prototype.getElementById = function ( strId ) {
  if ( document.getElementById ) {
    return document.getElementById( strId );
  } else if ( document.all ) {
    return document.all[ strId ];
  } else {
    return null;
  }
}

DOM.prototype.getIdentity = function ( obj ) {
	if ( obj ) {
		var strParent = this.getIdentity( obj.parentNode );
		if ( strParent ) {
  		return obj.tagName + "." + obj.className + "#" + obj.id + (( strParent != "" ) ? "<<" + strParent : "");
		} else {
			return  obj.tagName + "." + obj.className + "#" + obj.id;
		}
	} else {
		return "";
	}
}

DOM.prototype.getObject = function ( varObject ) {
	// Returns object, given id or object
  if ( varObject ) {
  	if ( varObject.constructor == String ) {
  		return this.getElementById( varObject );
  	} else {
  		return varObject;
  	}
  } else {
    return null;
  }
}

DOM.prototype.getParent = function ( obj ) {
  if ( obj ) {
    if ( obj.parentNode ) {
      return obj.parentNode;
    } else {
      return null;
    }
  } else {
    return null;
  }
}

DOM.prototype.getTextOf = function ( varObj ) {
  var obj = this.getBody( varObj );
  if ( obj ) {
    return obj.innerHTML;
  }
}

DOM.prototype.objectDifferences = function ( objOriginal, objChanged ) {
  var objDifferences = new Object();
  for ( var strAttribute in objChanged ) {  // Pick up insertions and changes
    if ( !objOriginal[ strAttribute ] ) {
      objDifferences[ strAttribute ] = objChanged[ strAttribute ];
    } else if ( !objOriginal[ strAttribute ] != objChanged[ strAttribute ] ) {
      objDifferences[ strAttribute ] = objChanged[ strAttribute ];
    }
  }
  for ( var strAttribute in objOriginal ) {  // Pick up deletions
    if ( !objChanged[ strAttribute ] ) {
      objDifferences[ strAttribute ] = "";  // Is there a better way to represent "deleted"?
    }
  }
  return objDifferences;
}

DOM.prototype.objectExcept = function( r_objOriginal, arrAttributesToExclude ) {
  if ( typeof( r_objOriginal ) != "undefined" && r_objOriginal.constructor == Object &&
       typeof( arrAttributesToExclude ) != "undefined" && arrAttributesToExclude.constructor == Array ) {
     for ( var intIndex = 0; intIndex < arrAttributesToExclude.length; intIndex++ ) {
       delete r_objOriginal[ arrAttributesToExclude[ intIndex ] ];
     }
  }
  return r_objOriginal;
}

DOM.prototype.objectEquals = function ( objFirstObject, objSecondObject ) {
  if ( typeof( objFirstObject ) != "undefined" && objFirstObject.constructor == Object &&
       typeof( objSecondObject ) != "undefined" && objSecondObject.constructor == Object ) {
    var blnEquals = true;
    for ( var strProperty in objSecondObject ) {
      blnEquals = blnEquals && ( objFirstObject[ strProperty ] == objSecondObject[ strProperty ] );
      if ( !blnEquals ) break;
    }
    return blnEquals;
  } else {
    return false;
  }
}

DOM.prototype.objectExcludingByRule = function( v_objBase, v_strItemVariable, v_strRule ) {
  var objResult = new Object();
  for ( var strItemZZZ in v_objBase ) { //Try to avoid name clash with supplied variable
    eval( v_strItemVariable + " = v_objBase[ '" + strItemZZZ + "' ];" );
    if ( eval( v_strRule ) ) {
      // exclude
    } else {
      objResult[ strItemZZZ ] = v_objBase[ strItemZZZ ];
    }
  }
  return objResult;
}

DOM.prototype.objectMerge = function( objBase, objToMerge ) {
  for ( var strAttribute in objToMerge ) {
    objBase[ strAttribute ] = objToMerge[ strAttribute ];
  }
  return objBase; // Not strictly necessary...
}

DOM.prototype.objectMergeExcept = function( objBase, objToMerge, arrExceptions ) {
  for ( var strAttribute in objToMerge ) {
    if ( ! this.arrayHasMember( arrExceptions, strAttribute ) ) {
      objBase[ strAttribute ] = objToMerge[ strAttribute ];
    }
  }
  return objBase; // Not strictly necessary...
}

DOM.prototype.objectMergeOnly = function( objBase, objToMerge, arrInclusions ) {
  for ( var strAttribute in objToMerge ) {
    if ( this.arrayHasMember( arrInclusions, strAttribute ) ) {
      objBase[ strAttribute ] = objToMerge[ strAttribute ];
    }
  }
  return objBase; // Not strictly necessary...
}

DOM.prototype.registerListener = function ( objElement, strEvent, funListener, blnUseCapture ) {
  if ( objElement && strEvent && funListener ) {
    if (objElement.addEventListener) {
      objElement.addEventListener( strEvent, funListener, blnUseCapture );
      return true;
    } else if (objElement.attachEvent) {
      return objElement.attachEvent( "on" + strEvent, funListener );
    } else {
      return eval( "objElement.on" + strEvent + " = funListener" );
    }
  } else {
    return false;
  }
}

DOM.prototype.registerListeners = function ( objElement, objHandlers, blnUseCapture ) {
  if ( objHandlers.constructor == Object ) {
    for ( var strEvent in objHandlers ) {
      var fnHandler = objHandlers[ strEvent ];
      this.registerListener( objElement, strEvent, fnHandler, blnUseCapture );
    }
  }
}

DOM.prototype.deregisterListener = function ( objElement, strEvent, funListener, blnUseCapture) {
  if ( objElement && strEvent && funListener ) {
    if (objElement.removeEventListener) {
      objElement.removeEventListener( strEvent, funListener, blnUseCapture  );
    } else if (objElement.attachEvent) {
      objElement.detachEvent( "on" + strEvent, funListener );
    }
  }
}

/**
 * General-purpose event handling that can be associated with the top-level handler by other objects.
 * @param {Object}  objElement     Object to run method against.
 * @param {String}  strEvent       Event to trigger event for.
 * @param {String}  strMethod      Method of object to trigger.
 * @param {Boolean} blnPersistent  Keep this event, or remove it after triggering.
 *                                 (If numeric, decrement until 0, and then deregister event )
 */
DOM.prototype.registerEvent = function ( objElement, strEvent, strMethod, blnPersistent ) {
  if ( this.objEvents[ strEvent ] )
    var arrEvents = this.objEvents[ strEvent ];
  else {
    var arrEvents = new Array();
  }
  arrEvents[ arrEvents.length ] = [ objElement, strMethod, blnPersistent ];
  this.objEvents[ strEvent ] = arrEvents;
}

DOM.prototype.deregisterEvent = function (  objElement, strEvent, strMethod ) {
  if ( this.objEvents[ strEvent ] ) {
    var arrEvents = this.objEvents[ strEvent ];
    for ( var intIndex = 0; intIndex < arrEvents.length; intIndex++ ) {
      var arrEvent = arrEvents[ intIndex ];
      if ( arrEvent[ 0 ] == objElement && arrEvent[ 1 ] == strMethod ) {
        arrEvents.splice( intIndex, 1 );        // Delete the registered event
        break;
      }
    }
  }
}

DOM.prototype.runEvents = function( strEvent, evt ) {
  if ( this.objEvents[ strEvent ] ) {
    var arrEvents = this.objEvents[ strEvent ];
    for ( var intIndex = 0; intIndex < arrEvents.length; intIndex++ ) {
      var arrEvent = arrEvents[ intIndex ];
      var objElement = arrEvent[ 0 ];
      var strMethod = arrEvent[ 1 ];
      var blnPersistent = arrEvent[ 2 ];
      if ( objElement && strMethod ) {
        var strStatement = "objElement." + strMethod + "( evt )";
        eval( strStatement );
        if ( blnPersistent ) {
          if ( blnPersistent.constructor == Number ) {
            blnPersistent--;
            if ( blnPersistent == 0 ) this.deregisterEvent( objElement, strEvent, strMethod );
          }
        } else {
          this.deregisterEvent( objElement, strEvent, strMethod );
        }
      } else {
        this.deregisterEvent( objElement, strEvent, strMethod );  // Try to clean up list
      }
    }
  }
}

/**
 * Traverse upwards through the parents of obj until the varMatch condition is met, unless
 * blnIgnoreStartingElement is false, in which case it is possible for the condition to 
 * match againts obj.
 * The parameter varMatch can be a String, when the value is expected to be a tag name. 
 * Under this circumstance the function seeks to find the first obj or ancestor of obj
 * that has the given tag.
 * The parameter varMatch can, alternatively, be an Object, comprising a collection of 
 * labelled values. The labels are considered to be valid attributes of any element in
 * the DOM tree and the values are tested against the corresponding attributes of each 
 * element (including obj, unless blnIgnoreStartingElement is true), until either a node
 * is found for which all attribute values match, or a null node is found.
 * 
 * It is important that callers of this procedure that expect to find a match test for a
 * null return, just in case the target DOM does not oblige.
 * 
 * Examples:
 * 
 * 1. dom.getFirstAncestorMatching( obj, "div", false );
 * Find the first element that is obj or an ancestor of obj that has a <DIV> tag.
 * 
 * 2. dom.getFirstAncestorMatching( obj, { 'tagName' : 'div', 'class' : 'Southstar-Panel' }, false );
 * Find the first element that is obj or an ancestor of obj that has a <DIV> tag and the specified class.
 * 
 * @param {Object}           obj
 * @param {Object or String} varToMatch
 * @param {Boolean}          blnIgnoreStartingElement
 */
DOM.prototype.getFirstAncestorMatching = function ( obj, varToMatch, blnIgnoreStartingElement ) {
  if ( obj ) {
    obj = this.getObject( obj );
    if ( obj == document || ( obj.nodeType && obj.nodeType == Node.DOCUMENT_NODE ) ) return null;
    if ( blnIgnoreStartingElement ) obj = dom.getParent( obj );
    switch ( varToMatch.constructor ) {
      case String:
        while ( obj && obj.tagName.toUpperCase() != varToMatch.toUpperCase() ) {
          obj = dom.getParent( obj );
          if ( obj == document || ( obj.nodeType && obj.nodeType == Node.DOCUMENT_NODE ) ) return null;
        }
        return obj;
        
      case Object:
        var blnFound = false;
        while ( obj && !blnFound ) {
          var blnMatch = true;
          for ( var strItem in varToMatch ) {
            var varAttributeValue = obj[ strItem ];
            if ( varAttributeValue ) {
              if ( varAttributeValue.constructor == String ) {
                blnMatch = blnMatch && varAttributeValue.toUpperCase() == (varToMatch[ strItem ]).toUpperCase();
              } else { 
                blnMatch = blnMatch && varAttributeValue == varToMatch[ strItem ];
              }
            } else {
              blnMatch = false;
            }
          }
          blnFound = blnMatch;
          if ( !blnFound ) obj = dom.getParent( obj );
        }
        return obj;
    }
  } else {
    return null;
  }
}

DOM.prototype.getTagName = function ( objElement ) {
  return (objElement.tagName)
         ? objElement.tagName   // Does not work with N4
         : objElement.nodeName;
}

DOM.prototype.getEvent = function ( evt ) {
  if ( !evt ) evt = new Event(); //evt = window.event
  return evt;
}

DOM.prototype.getEventKey = function ( evt ) {
  if ( evt.keyCode ) {
    return evt.keyCode;
  } else {
    return evt.which;
  }
}

// The following event-related methods are deprecated in favour of the Event prototype extensions.
DOM.prototype.getEventX = function ( evt ) {
  var intX = 0;
  if ( evt ) {
    if ( evt.pageX ) {
      intX = evt.pageX;
    } else if ( evt.clientX ) {
      intX = evt.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
    }
  }
  return intX;
}

DOM.prototype.getEventY = function ( evt ) {
  var intY = 0;
  if ( evt ) {
    if ( evt.pageY ) {
      intY = evt.pageY;
    } else if ( evt.clientY ) {
      intY = evt.clientY + document.body.scrollTop + document.documentElement.scrollTop;
    }
  }
  return intY;
}

DOM.prototype.getEventPosition = function ( evt ) {
  var objPosition = new Object();
  objPosition[ 'x' ] = this.getEventX( evt );
  objPosition[ 'y' ] = this.getEventY( evt );
  return objPosition;
}

DOM.prototype.getEventTargetMovedFrom = function ( evt ) {
  return evt.relatedTarget || evt.fromElement;
}

DOM.prototype.getEventTargetMovedTo = function ( evt ) {
  return evt.relatedTarget || evt.toElement;
}

DOM.prototype.getEventTarget = function ( objThis, evt ) {
	var objTarget = objThis;
  if ( !evt ) { 
    evt = window.event;
  }
	if ( evt ) {
    if ( evt.target ) {
      objTarget = evt.target;
    }
		else if (evt.srcElement) {
			objTarget = evt.srcElement;
		}
    if ( objTarget.nodeType == 3 ) {    // If target is a text node
      objTarget = objTarget.parentNode; // then return the parent
    }
	}
  return objTarget;
}

DOM.prototype.getEventButton = function ( evt ) {
  // TODO: getEventButton needs a complete rewrite
  var intButton = -1;
  if ( evt.which ) {
    switch ( evt.which ) {
      case 0: return false;
      case 1: return "LEFT";
      case 2: return "MIDDLE";
      case 3: return "LEFT,MIDDLE";
      case 4: return "RIGHT";
      case 5: return "LEFT,RIGHT";
      case 6: return "MIDDLE,RIGHT";
      case 7: return "LEFT,MIDDLE,RIGHT";
    }
  } else if ( evt.button ) {
    switch ( evt.button ) {
      case 0: return "LEFT";
    }
  }
}

DOM.prototype.cancelEvent = function ( evt ) {
  evt.cancelBubble = true;
  if ( evt.stopPropagation ) evt.stopPropagation();
}

DOM.prototype.eventIsOutside = function ( evt, obj ) {
  if ( obj.getRootElement ) {
    var objFrame = obj.getRootElement();
  } else {
    var objFrame = obj;
  }
  var pnt = dom.getEventPosition( evt );
  var rct = dom.getObjectRectangle( obj );
  return rct.doesNotContain( pnt );
}

DOM.prototype.objUniqueIds = new Object();

DOM.prototype.getUniqueId = function () {
  if (document.uniqueID) {
    return document.uniqueID;
  } else {
    // Attempts to provide a uniqueID function we can use.
    // Not that this mechanism tries to guarantee uniqueness, but at the risk of 
    // a performance hit as the stored object fills up, and the further risk of 
    // never exiting if we use all available numbers.
    var numRandom = Math.random();
    var strRandom = (numRandom.toString()).substring(2);
    while ( this.objUniqueIds[ "K" + strRandom ] ) {
      numRandom = Math.random();
      strRandom = (numRandom.toString()).substring(2);
    }
    this.objUniqueIds[ "K" + strRandom ] = strRandom;
    return strRandom;
  }
}

/***************************************************************
 * Browser Detects
 * Only use as a last resort...

DOM.prototype.isIE = (!DOM.prototype.isOpera && navigator.userAgent.indexOf('MSIE') != -1);
DOM.prototype.isKnoqueror = (navigator.userAgent.indexOf("Konqueror")!=-1);
DOM.prototype.isNetscape = (navigator.appName.indexOf("Netscape") != -1);
DOM.prototype.isOpera = (navigator.userAgent.indexOf('Opera') != -1);
DOM.prototype.isSafari = function () { }
 ***************************************************************/

DOM.prototype.isValidCSSUnit = function( strUnit ) {
  var blnResult = false;
  
  switch ( strUnit.toLowerCase() ) {
    case "em":
    case "ex":
    case "px":
    case "in":
    case "cm":
    case "mm":
    case "pt":
    case "pc":
      blnResult = true;
      break;
  }
  return blnResult;
}

DOM.prototype.setAttributes = function( v_objAttributes, v_arrNames, v_varValues ) {
  // Set named attributes to single value or corresponding entry in array or object of values
  for ( var i in v_arrNames ) {
    var strName = v_arrNames[ i ];
    switch ( v_varValues.constructor ) {
      case Array: 
        v_objAttributes[ strName ] = v_varValues[ i ];
        break;
      case Boolean:
      case Date:
      case Number:
      case String:
        v_objAttributes[ strName ] = v_varValues;
        break;
      case Object:
        v_objAttributes[ strName ] = v_varValues[ strName ];
        break;
      default:
        // Erroneous arguments. We fail silently. Programmer should use debug to identiry and prevent this case
    }
  }  
}

DOM.prototype.setElementAttribute = function( v_varElement, v_strAttribute, v_varValue ) {
  var objElement = this.getObject( v_VarElement );
  if ( objElement ) {
    eval( "objElement." + v_strAttribute + " = '" + v_varValue + "';" );
  }
}

DOM.prototype.setUnit = function( varValue, strUnit ) {
  var strValue = varValue.toString();
  strOldUnit = strValue.substring( strValue.length - 2, 2 );
  if ( this.isValidCSSUnit( strOldUnit ) ) {
    if ( this.isValidCSSUnit( strUnit ) ) {
      strValue = strValue.substring( 1, strValue.length - 2 ) + strUnit;
    } else {
      // strValue cannot have an invalid unit applied, so leave it unchanged
    }
  } else {
    if ( this.isValidCSSUnit( strUnit ) ) {
      strValue = strValue + strUnit;
    } else {
      // New unit is invalid and old value has no valid unit, so leave unchanged
    }
  }
  return strValue;
}

DOM.prototype.autoSize = function( varElement ) {
  var objElement = dom.getObject( varElement );
  var objParent = dom.getParent( objElement );
  var varHeight = dom.getHeight( objParent );
  while ( (varHeight  == 0 || varHeight == "") && objParent.tagName != "BODY" ) {
    objParent = dom.getParent( objParent );
    varHeight = dom.getHeight( objParent );
  }
  objElement.height = varHeight * 0.9;
  objElement.width = dom.getWidth( objParent ) * 0.95;
}

DOM.prototype.getSizeAsOffset = function ( varElement ) {
  var objElement = dom.getObject( varElement );
  return { 'x' : dom.getWidth( objElement ), 'y' : dom.getHeight( objElement ) };
}

DOM.prototype.getHeight = function ( varElement ) {
  var objElement = dom.getObject( varElement );
  if ( objElement.offsetHeight != undefined ) {
    return objElement.offsetHeight;
  } else if ( objElement.height != undefined ) {
    return objElement.height;
  } else if ( objElement.clientHeight != undefined ) {
    return objElement.clientHeight;
  }
}

DOM.prototype.getWidth = function ( varElement ) {
  var objElement = dom.getObject( varElement );
  if ( objElement.offsetWidth != undefined) {
    return objElement.offsetWidth;
  } else if ( objElement.width != undefined ) {
    return objElement.width;
  } else if ( objElement.clientWidth != undefined ) {
    return objElement.clientWidth;
  }
}

DOM.prototype.getBottom = function ( varElement ) {
  var objElement = dom.getObject( varElement );
  return dom.getTop( objElement ) + dom.getHeight( objElement );
}

DOM.prototype.getLeft = function ( varElement ) {
  var objElement = dom.getObject( varElement );
  return ( objElement.offsetLeft != undefined ) 
         ? objElement.offsetLeft
         : ( objElement.clientLeft != undefined )
           ? objElement.clientLeft
           : objElement.pageLeft;
}

DOM.prototype.getRight = function ( varElement ) {
  var objElement = dom.getObject( varElement );
  return dom.getLeft( objElement ) + dom.getWidth( objElement );
}

DOM.prototype.getTop = function ( varElement ) {
  var objElement = dom.getObject( varElement );
  return ( objElement.offsetTop != undefined ) 
         ? objElement.offsetTop
         : ( objElement.clientTop != undefined )
           ? objElement.clientTop
           : objElement.pageTop;
}

DOM.prototype.getBottomLeft = function( varElement ) {
  return { 'x' : dom.getLeft( varElement ), 'y' : dom.getBottom( varElement ) };
}

DOM.prototype.getBottomRight = function( varElement ) {
  var objElement = dom.getObject( varElement );
  var pntPosition = this.getTopLeft( varElement );
  pntPosition.offsetBy( objElement.offsetWidth, objElement.offsetHeight )
  return pntPosition;
}

DOM.prototype.getTopLeft = function ( varElement, blnTraverseParentTree ) {
  var objElement = dom.getObject( varElement );
  var objPosition = new Point( objElement.offsetLeft, objElement.offsetTop );
  while ( objElement.offsetParent ) {
    objPosition.offsetBy( objElement.offsetParent.offsetLeft, objElement.offsetParent.offsetTop );
    objElement = objElement.offsetParent;
  }
  return objPosition;
}

DOM.prototype.getTopRight = function ( varElement ) {
  var objElement = dom.getObject( varElement );
  var objPosition = dom.getTopLeft( objElement, true );
  objPosition.offsetBy( objElement.offsetWidth, objElement.offsetHeight );
  return objPosition;
}

DOM.prototype.getObjectRectangle = function ( varElement ) {
  if ( varElement && varElement.constructor == Rectangle ) {
    return varElement;
  } else {
    var objElement = dom.getObject( varElement );
    return new Rectangle( dom.getLeft( objElement ), dom.getTop( objElement ), dom.getRight( objElement ), dom.getBottom( objElement ) );
  }
}

DOM.prototype.isAbsolutePosition = function ( objElement ) {
  return ( objElement.style.position.toLowerCase() == "absolute" );
}

DOM.prototype.isRelativePosition = function ( objElement ) {
  return ( objElement.style.position.toLowerCase() == "relative" );
}

DOM.prototype.moveBy = function ( varElement, intDeltaX, intDeltaY ) {
  var objElement = dom.getObject( varElement );
  if ( objElement && objElement.style && objElement.offsetLeft ) {
    var intCurrentX = ( objElement.style.left == "" ) ? dom.getLeft( objElement ) : parseInt( objElement.style.left );
    var intCurrentY = ( objElement.style.top  == "" ) ? dom.getTop( objElement )  : parseInt( objElement.style.top );
    objElement.style.left = (intCurrentX + intDeltaX) + "px";
    objElement.style.top = (intCurrentY + intDeltaY) + "px";
  }  
}

DOM.prototype.moveOver = function ( varToMove, varElement, objOffset, objSize ) {
  var objToMove = dom.getObject( varToMove );
  if ( objToMove && varElement ) {
    var pntTargetPosition = this.getTopLeft( varElement );
    if ( objOffset ) {
      pntTargetPosition.offsetBy( objOffset.x, objOffset.y );
    }
    dom.moveTo( objToMove, pntTargetPosition.getX(), pntTargetPosition.getY() );
    if ( objSize ) {
      if ( objSize.height && objSize.width ) {
        dom.resizeTo( objToMove, objSize.width, objSize.height );
      } else {
        // Resize to width and height of varElement
        this.setWidth( objToMove, this.getWidth( varElement ) );
        this.setHeight( objToMove, this.getHeight( varElement ) );
      }
    }
  }
}

DOM.prototype.moveTo = function ( varElement, intX, intY ) {
  var objElement = dom.getObject( varElement );
  if ( objElement && objElement.style ) {
    objElement.style.left = this.setUnit( intX, "px" );
    objElement.style.top = this.setUnit( intY, "px" );
  }
}

DOM.prototype.resizeBy = function( varElement, objOffset, strResizer ) {
  var objElement = this.getObject( varElement );
  if ( objOffset.constructor != Point ) objOffset = new Point( objOffset );
  for ( var intIndex = 0; intIndex < strResizer.length; intIndex++ ) {
    switch ( strResizer.charAt( intIndex ).toUpperCase() ) {
      case 'N':
        objElement.style.height = this.setUnit( dom.getHeight( objElement ) + objOffset.intY, "px" );
        objElement.style.top = this.setUnit( dom.getTop( objElement ) + objOffset.intY, "px" );
        break;
      
      case 'S':
        objElement.style.height = this.setUnit( dom.getHeight( objElement ) + objOffset.intY, "px" );
        objElement.style.bottom = this.setUnit( dom.getBottom( objElement ) + objOffset.intY, "px" );
        break;
      
      case 'E':
        objElement.style.width = this.setUnit( dom.getWidth( objElement ) + objOffset.intX, "px" );
        objElement.style.right = this.setUnit( dom.getRight( objElement ) + objOffset.intX, "px" );
        break;
      
      case 'W':
        objElement.style.width = this.setUnit( dom.getWidth( objElement ) + objOffset.intX, "px" );
        objElement.style.left = this.setUnit( dom.getLeft( objElement ) + objOffset.intX, "px" );
        break;
    }
  }
}

DOM.prototype.resizeTo = function( varElement, objSize ) {
  if ( varElement && objSize && objsize.width && objSize.height ) {
    this.setWidth( varElement, objSize.width );
    this.setHeight( varElement, objSize.height );
  }
}

DOM.prototype.resizeToPoint = function( varElement, objPoint, strResizer ) {
  var objElement = this.getObject( varElement );
  var rctRectangle = dom.getObjectRectangle( objElement );
  for ( var intIndex = 0; intIndex < strResizer.length; intIndex++ ) {
    switch ( strResizer.charAt( intIndex ).toUpperCase() ) {
      case 'N':
        objElement.style.top = this.setUnit( objPoint.getY(), "px" );
        objElement.style.height = this.setUnit( rctRectangle.getTop() - objPoint.getY(), "px" );
        //this.resizeDimension( objElement, "HEIGHT", this.getTop( objElement ) - objPoint.getY() );
        break;
      
      case 'S':
        objElement.style.height = this.setUnit( objPoint.getY() - rctRectangle.getBottom, "px" );
        objElement.style.bottom = this.setUnit( objPoint.getY(), "px" );
        break;
      
      case 'E':
        objElement.style.width = this.setUnit( objPoint.getX() - rctRectangle.getLeft(), "px" );
        ////objElement.style.right = this.setUnit( objPoint.getX(), "px" );
        break;
      
      case 'W':
        //objElement.style.left = this.setUnit( objPoint.getX(), "px" );
        //objElement.style.width = this.setUnit( objPoint.getX() - rctRectangle.getRight(), "px" );
        break;
    }
  }
}

/**************************************************************
 * Drag and Drop Simulation
 * The interface objects need to use this drag and drop 
 * functionality includes setting up the following:
 *   <tag>.applicationObject  = <obj>
 *   <obj>.isDraggable        = true
 *   <obj>.isResizable        = true
 **************************************************************/
DOM.prototype.onMouseDown = function ( evt ) {
  var objTarget = dom.getEventTarget( this, evt );
  var objApplicationObject = objTarget.applicationObject;
  dom.cancelEvent( evt );
  if ( objApplicationObject ) {
    objApplicationObject.blnDragging = (objApplicationObject.isDraggable);
    objApplicationObject.blnResizing = (objApplicationObject.isResizable);
    if ( objApplicationObject.blnDragging || objApplicationObject.blnResizing ) {
      var objRootElement = objApplicationObject.getRootElement();
      objApplicationObject.objLastMousePosition = dom.getEventPosition( evt );
      dom.registerListener( objRootElement, "mousemove", dom.onMouseMove, false );
      dom.registerListener( objRootElement, "mouseup", dom.onMouseUp, false );
    }
  }
  // Process generic events
  dom.runEvents( "mousedown", evt );
}

DOM.prototype.onMouseMove = function ( evt ) {
  var objTarget = dom.getEventTarget( this, evt );
  var objApplicationObject = objTarget.applicationObject;
  if ( objApplicationObject && objApplicationObject.objLastMousePosition ) {
    if ( objApplicationObject.blnResizing  && objTarget.resizer ) {
      var objMousePosition = new Point( dom.getEventPosition( evt ) );
      dom.resizeToPoint( objApplicationObject.getRootElement(), objMousePosition, objTarget.resizer );
      
      objApplicationObject.objLastMousePosition = objMousePosition;
    } else if ( objApplicationObject.blnDragging ) {
      var objMousePosition = dom.getEventPosition( evt );
      var rctBoundingRectangle = ( objTarget.dragBoundedBy ) ? dom.getObjectRectangle( objTarget.dragBoundedBy ) : false;
      if ( objMousePosition && (!rctBoundingRectangle || ( rctBoundingRectangle.contains( new Point( objMousePosition ) ) ) ) ) {
        dom.moveBy( objApplicationObject.getRootElement(), 
                    objMousePosition.x - objApplicationObject.objLastMousePosition.x, 
                    objMousePosition.y - objApplicationObject.objLastMousePosition.y );
        objApplicationObject.objLastMousePosition = objMousePosition;
      }
    }
  }
  // Process generic events
  dom.runEvents( "mousemove", evt );
}

DOM.prototype.onMouseUp = function ( evt ) {
  var objTarget = dom.getEventTarget( this, evt );
  var objApplicationObject = objTarget.applicationObject;
  if ( objApplicationObject ) {
    objApplicationObject.blnDragging = false;
    objApplicationObject.blnResizing = false;
    //objApplicationObject.objLastMousePosition = null;
    var objRootElement = objApplicationObject.getRootElement();
    dom.deregisterListener( objRootElement, "mousemove", dom.onMouseMove, false );
    dom.deregisterListener( objRootElement, "mouseup",   dom.onMouseUp,   false );
  }
  // Process generic events
  dom.runEvents( "mouseup", evt );
}

DOM.prototype.onShowHide = function( evt ) {
  // This can be added as a listener to an object so that the associated event toggles the visibility of its children
  var objTarget = dom.getEventTarget( this, evt );
  objTarget.visibility = !objTarget.visibility
  var blnVisibility = objTarget.visibility;
  dom.setVisibility( objTarget, blnVisibility );
}

DOM.prototype.redrawElement = function ( varElement) {
  // Force a browser redraw. From http://ajaxian.com/archives/forcing-a-ui-redraw-from-javascript
  var objElement = $this.getObject(varElement);
  if ( objElement ) {
    var objNode = document.createTextNode(' ');
  }
  objElement.appendChild( objNode );
  (function(){objNode.parentNode.removeChild(objNode)}).defer();
 return objElement;
}

DOM.prototype.raiseException = function( v_objObject, v_strMessage ) {
  if ( v_objObject.message ) {
    v_objObject.message = v_strMessage;
  }
  if ( v_objObject.blnExceptionRaised ) {
    v_objObject.blnExceptionRaised = true;
  }
}

DOM.prototype.resizeDimension = function( varElement, strDimension, intAmount ) {
  var objElement = this.getObject( varElement );
  switch ( strDimension.toUpperCase() ) {
    case 'BOTTOM': 
      objElement.style.bottom = objElement.offsetBottom + intAmount;
      break;
    
    case 'HEIGHT':
      objElement.style.height = objElement.offsetHeight + intAmount;
      break;
      
    case 'LEFT':
      objElement.style.left = objElement.offsetLeft + intAmount;
      break;
    
    case 'RIGHT':
      objElement.style.right = objElement.offsetRight + intAmount;
      break;
    
    case 'TOP':
      objElement.style.top = objElement.offsetTop + intAmount;
      break;
    
    case 'WIDTH':
      objElement.style.width = objElement.offsetWidth + intAmount;
      break;
    
  }
}

DOM.prototype.setElementVisibility = function( r_objElement, v_blnVisible ) {
  if ( r_objElement != null ) {
    if ( v_blnVisible == true ) {
      r_objElement.style.visibility = "visible";
      r_objElement.style.display = "block";
    } else {
      r_objElement.style.visibility = "hidden";
      r_objElement.style.display = "none";
    }
  }
}

DOM.prototype.setHeight = function( varElement, intHeight ) {
  var objElement = this.getObject( varElement );
  objElement.style.height = intHeight;
}

DOM.prototype.setWidth = function( varElement, intWidth ) {
  var objElement = this.getObject( varElement );
  objElement.style.width = intWidth;
}

DOM.prototype.setVisibility = function ( varElement, blnVisible, blnIncludeSelf ) {
  var objTarget = this.getObject( varElement );
  if (objTarget) {
    if ( blnIncludeSelf ) {
      if ( blnVisible ) {
        objTarget.style.visibility = "visible";
        objTarget.style.display = (blnVisible.constructor == String) ? blnVisible :"block";
      } else {
        objTarget.style.visibility = "hidden";
        objTarget.style.display = "none";
      }
    }
    var intNumberOfElements = objTarget.childNodes.length;
    var objChild;
    
    for (var intIndex = 0; intIndex < intNumberOfElements; intIndex++) {
      objChild = objTarget.childNodes[intIndex];
      if (objChild.nodeName != "#text") {
        if (blnVisible) {
          objChild.style.visibility = (blnVisible.constructor == String) ? blnVisible :"block";
          objChild.style.display = "block";
        }
        else {
          objChild.style.visibility = "hidden";
          objChild.style.display = "none";
        }
      }
    }
  }
}

DOM.prototype.writetag = function ( objContainer, str, strTag, objAttributes, blnClearFirst ) {
  var objContainer = dom.getObject( objContainer ); // Turns string id or object into object
  // Write a string to the given container
  if (objContainer) {
    if ( blnClearFirst ) {
      this.eraseChildren( objContainer );
    }
    if ( !strTag ) strTag = "div";
    var objElement = dom.createElement( objContainer, strTag, false, str );
    if (objAttributes) {
      for (var strAttribute in objAttributes) {
        if (objAttributes[strAttribute]) {
          objElement.setAttribute( strAttribute, objAttributes[strAttribute] );
        }
      }
    }
  }
}

DOM.prototype.use = function ( strURL, strType ) {
  // 20071216 JTB Replaced writeln (which breaks in xHTML) with DOM methods!
  var objDocument = document.documentElement;
  var objHead = dom.getElementById( "head" );  // Why doesn't this work?
  if ( !objHead )                              // Add braces to belt...
    for ( var intIndex = 0; intIndex < objDocument.childNodes.length; intIndex++ ) {
      var objElement = objDocument.childNodes[ intIndex ];
      if ( objElement.tagName && objElement.tagName.toLowerCase() == "head" ) objHead = objElement;
    }
  if ( !this.used[ strURL ] ) {
    switch ( strType.toLowerCase() ) {
      case "text/javascript":
        this.used[ strURL ] = this.createElement( objHead, "script", { 'type' : strType,
                                                                       'src'  : strURL 
                                                                     }
                                                );
        break;
      case "text/css":
        this.used[ strURL ] = this.createElement( objHead, "link", { 'rel'   : 'Stylesheet',
                                                                     'type' : strType,
                                                                     'href'  : strURL 
                                                                   }
                                                );
        break;
    }
  }
}

DOM.prototype.useJavascript = function ( strPath, varURL ) {
  if ( varURL ) {
    switch ( varURL.constructor ) {
      case String:
        this.use( ((strPath) ? strPath + "/" : "") + varURL + ".js", "text/javascript" );
        break;
      case Array:
        for ( var intIndex in varURL ) {
          this.use( ((strPath) ? strPath + "/" : "") + varURL[ intIndex ] + ".js", "text/javascript" );
        }
        break;
    }
  }
}

DOM.prototype.run = function () {
  dom.loadCookies();
  dom.registerListener( window, "mousedown", dom.onMouseDown, false );
}

var dom = new DOM();
dom.run();
