/*
 * Copyright 2003-2007 inxire GmbH. All rights reserved.
 * ------------------------------------------------------
 * Version: $Id: domUtility.js,v 1.9.2.1 2007/09/14 17:43:23 mhorst Exp $
 *
 * Generic utility functions for HTML DOM tree search and manipulations.
 *
 * Only DOM Level 1 functions are used here !!
 *
 * Author: Hans-Martin Keller
 */


// Defaults
var defaultEnvelopeClass = 'buildingBlock';
var unmovableClass       = 'unmovable';
var submitted            = false;

 
/*
 * DOM Navigate and Manipulate
 * **********************************************************************
 *
 * Find ancestor, child and siblings by CCS class or tag name. 
 *
 * In addition some insert, copy and remove functions are implemented
 * and some utilities to search-and-replace attribute values in sub-trees
 */

/*
 * Utility function to match given class and tag name
 *
 * @param node        some DOM node, mandatory
 * @param className   CSS class of the ancestor, null means any (String)
 * @param tagName     tag name of the ancestor, null  means any (String or RegExp)
 */
function matchClassAndName(node, className, tagName) 
{
  // alert("matchClassAndName:" + "\nnode = " + node + ", " + node.id  + ", " + node.className + "\nclassName = " + className + "\ntagName = " + tagName);

  return (  ( !tagName   || (node.tagName && ((tagName.test && tagName.test(node.tagName)) || node.tagName == tagName)) )
         && ( !className || (node.className && node.className.search( new RegExp("\\b"+className+"\\b","i") ) >= 0 ) ) )
}


/*
 * Returns the closest ancestor with given CSS class and tag name.
 * The given node is also checked and may be returned !!
 *
 * @param descendant  some descendant of the desired node
 * @param className   CSS class of the ancestor, null means any
 * @param tagName     tag name of the ancestor, null  means any
 * @return  the ancestor found, or null
 */
function findAncestorOrSelf(descendant, className, tagName)
{
  var ancestor = descendant;
  var max = 1000;

  // alert("findAncestorOrSelf:" + "\ndescendant = " + descendant + ", " + descendant.id + "\nclassName = " + className + "\ntagName = " + tagName);

  while (ancestor && max > 0) 
  {
    // alert('ancestor : ' + ancestor.nodeName + ', ' + ancestor.id + ', ' + ancestor.className);
    if ( matchClassAndName(ancestor, className, tagName) )
    {
      // alert("found : "  + ancestor.nodeName + ', ' + ancestor.id + ', ' + ancestor.className);
      return ancestor;
    }
    ancestor = ancestor.parentNode;
    max--;
  }
  return null;
}


/*
 * Returns the next sibling with given CSS class and tag name.
 * The current child ist NOT checked !!
 *
 * @param child       some child to start with
 * @param className   CSS class of the ancestor, null means any
 * @param tagName     tag name of the ancestor, null  means any
 * @param skipOther   if true, non-matching elements are skip (default is to only skip text nodes)
 * @return  the sibling found, or null
 */
function findNextSibling(child, className, tagName, skipOther)
{
  var sibling = child.nextSibling;

  while (sibling)
  {
    // alert('sibling : ' + sibling.nodeName + ', ' + sibling.id + ', ' + sibling.className);
    if ( matchClassAndName(sibling, className, tagName) )
    {
      // alert("found : "  + sibling.nodeName + ', ' + sibling.id + ', ' + sibling.className);
      return sibling;
    }
    // alert("check text:" + "\nnodeType = " + sibling.nodeType + "\nNode.TEXT_NODE = " + sibling.TEXT_NODE);
    if (!skipOther && sibling.nodeType == 1) return null;
    sibling = sibling.nextSibling;
  }
  return null;
}


/*
 * Returns the previous sibling with given CSS class and tag name.
 * The current child ist NOT checked !!
 *
 * @param child       some child to start with
 * @param className   CSS class of the ancestor, null means any
 * @param tagName     tag name of the ancestor, null  means any
 * @param skipOther   if true, non-matching elements are skip (default is to only skip text nodes)
 * @return  the sibling found, or null
 */
function findPreviousSibling(child, className, tagName, skipOther)
{
  var sibling = child.previousSibling;

  while (sibling)
  {
    // alert('sibling : ' + sibling.nodeName + ', ' + sibling.id + ', ' + sibling.className);
    if ( matchClassAndName(sibling, className, tagName) )
    {
      // alert("found : "  + sibling.nodeName + ', ' + sibling.id + ', ' + sibling.className);
      return sibling;
    }
    // alert("check text:" + "\nnodeType = " + sibling.nodeType + "\nNode.TEXT_NODE = " + sibling.TEXT_NODE);
    if (!skipOther && sibling.nodeType == 1) return null;
    sibling = sibling.previousSibling;
  }
  return null;
}


/*
 * Search for a child node with given CSS class and tag name.
 * This method may be very exspensive, if depth is specified.
 * Use with care !!
 *
 * @param parent      some node to start with
 * @param className   CSS class of the ancestor, null means any
 * @param tagName     tag name of the ancestor, null  means any (must be upper case !!)
 * @param attrName  name of attribute to be searched
 * @param pattern   matching pattern, should be RegExp
 * @param depth       the search depth for recusive search
 * @return  the descendant found, or null
 */
function findDescendant(parent, className, tagName, attrName, pattern, depth)           // match self ??
{
  var childNodes = parent.childNodes;

  if (childNodes) 
  {
    for (var i=0; i<childNodes.length; i++) 
    {
      var child = childNodes[i];
      if ( matchClassAndName(child, className, tagName) )
      {
        // alert("findDescendant: " + child + ", " + child.id + ", " + child.className
        //     + "\nnodeType = " + child.nodeType
        //     + "\nclassName = " + className + "\ntagName = " + tagName 
        //     + "\nattrName = " + attrName + "\npattern = " + pattern);
        if (!attrName) {
          // alert("found : " + child.nodeName);
          return child;
        }
        else {
          var value = child.getAttribute(attrName);
          // alert("found : " + child.nodeName + "\nvalue = " + value);
          if (value && value.search && (value.search(pattern) >= 0)) return child;
        }
      }
      if (depth && depth > 1) 
      {
        var grandchild = findDescendant(child, className, tagName, attrName, pattern, depth-1);
        if (grandchild) return grandchild;
      }
    }
  }
  return null;    
}


/*
 * Checks, if the given node is an ancestor of the reference node.
 *
 * @param node        some potential parent of the reference node
 * @param referenceNode  the reference node
 * @return  true, if the node is an ancestor of the reference node, but not the reference node itself
 */
function isAncestor(node, referenceNode)
{
  var ancestor = referenceNode.parentNode;
  var max = 1000;

  while (ancestor && max > 0) 
  {
    if ( ancestor == node )
    {
      return true;
    }
    ancestor = ancestor.parentNode;
    max--;
  }
  return false;
}


/*
 * Search and replace given attribute value in sub-tree.
 * This method is also quite expensive ...
 *
 * Examples:
 * searchAndReplaceAttribute(root, null, null, 'id', /^ioc_delete_on_use_/g, '')
 * searchAndReplaceAttribute(root, null, null, 'id', new RegExp('^payload_id$', "i"), newId)
 *
 * @param node      start node of subtree
 * @param className   CSS class of the ancestor, null means any
 * @param tagName     tag name of the ancestor, null  means any (must be upper case !!)
 * @param attrName  name of attribute to be searched, *** use lower-case letters in IE ***
 * @param pattern   matching pattern, should be RegExp
 * @param newValue  replacement
 */
function searchAndReplaceAttribute(node, className, tagName, attrName, pattern, newValue)
{
  if (node && node.nodeType == 1) {

    // alert("searchAndReplace: " + node + ", " + node.id + ", " + node.className
    //      + "\nnodeType = " + node.nodeType
    //      + "\nclassName = " + className + "\ntagName = " + tagName 
    //      + "\nattrName = " + attrName + "\npattern = " + pattern + "\nnewValue = " + newValue);

    if (matchClassAndName(node, className, tagName)) {
      if (attrName) {
        var value = node.getAttribute(attrName);
        // alert("investigate node : " + node + ", " + node.id + ", " + node.className + "\nattrName = " + attrName + "\nvalue = " + value);
        if (value && value.search && (value.search(pattern) >= 0)) {
          var value2 = value.replace(pattern, newValue);
          // alert("Attribute found and replaced on " + node.nodeName + ", " + node.id + ", " + node.className
          //     + "\nnodeType = " + node.nodeType
          //     + "\nclassName = " + className + "\ntagName = " + tagName 
          //     + "\n" + attrName + " : " + value + " -> " + value2); 
          node.setAttribute(attrName, value2);
        }
      }
      else {
        var attributes = node.attributes;
        var len = (attributes && attributes.length) ? attributes.length : 0;
        for (var i=0; i<len; i++) {
          var attr = attributes[i];
          var value = attr.nodeValue;
          if (value && value.search && (value.search(pattern) >= 0)) {
            var value2 = value.replace(pattern, newValue);
            // alert(node.nodeName + '(' + i + '): ' + attr.nodeName + " = " + value + ' -> ' + value2);
            attr.nodeValue = value2;
            // node.setAttribute(attr.nodeName, value2);
          }
        }
      }
    }
    var childNodes = node.childNodes;
    if (childNodes) {
      for (var i=0; i<childNodes.length; i++) {
        searchAndReplaceAttribute(childNodes[i], className, tagName, attrName, pattern, newValue);
      }
    }
  }
}


/**
 * This function replaces the given element by a newly created one.
 * All child nodes a maintained, if the old and new nodes are of element type.
 *
 * @param  node      the node to be replaced
 * @param  nodeValue class name for element nodes, text value for text nodes, optional
 * @param  nodeName  name of new node, use '#text' for text nodes
 * @return the newly created node with all childs appended, or null
 */
function replaceByNewNode(node, nodeValue, nodeName)
{
  if (!node || !nodeName) return null;

  var ownerDocument = node.ownerDocument;
  if (!ownerDocument) ownerDocument = node.document;  // ownerDocument not supported by IE 5.5

  var newNode;
  if (nodeName == '#text' ) {
    //alert("|"+nodeName+"|"+nodeValue+"|");
    newNode = ownerDocument.createTextNode(nodeValue);
  }
  else if (nodeName == '#comment') {
    newNode = ownerDocument.createComment(nodeValue);
  }
  else {
    newNode = ownerDocument.createElement(nodeName);
    if (nodeValue) newNode.className = nodeValue;
  } 

  node.parentNode.insertBefore(newNode, node);
  if (node.nodeType == 1 && newNode.nodeType == 1)
      while(node.hasChildNodes()) {
        newNode.appendChild(node.firstChild);
      }
  node.parentNode.removeChild(node);
  return newNode;
}


/*
 * Replace given node by its child nodes. The node itself is removed from
 * DOM tree but all its children remain.
 * @return  the number of child nodes moved
 */
function replaceByChildren(node) 
{
  var count = 0;
  if (node && node.nodeType == 1 && node.parentNode) 
  {
    var parent = node.parentNode;
    while (count < 10000 && node.hasChildNodes())      // some sanity limit
    {
      var grandchild = node.firstChild;
      node.removeChild(grandchild);
      parent.insertBefore(grandchild, node);
      count++;
    }
    parent.removeChild(node);
  }
  return count;
}


/*
 * Remove the given node from DOM tree
 *
 * @param node  the node to be removed
 */
function removeNode(node)
{
  if (!node) return;
  var parent = node.parentNode;
  // alert("remove node : " + node + ", " + node.nodeName + ", class = " + node.className);
  if (node.style) node.style.display="none";
  parent.removeChild(node);
}


/*
 * Remove all descandants matching given class and tag name. The given node
 * itself is not tested.
 *
 * @param node        start node of subtree
 * @param className   CSS class of the ancestor, null means any (String)
 * @param tagName     tag name of the ancestor, null  means any (String or RegExp, must be upper case !!)
 * @param newNodeValue  class name of substitute element or text value for text nodes, optional
 * @param newNodeName   node name of substitute node, use '#text' for text node, optional
 */
function removeDescendants(node, className, tagName, newNodeValue, newNodeName)
{
  if (node && node.nodeType == 1) {
    if (node.childNodes) {
      for (var i=node.childNodes.length-1; i >= 0; i--) {
        var child = node.childNodes[i];
        if (matchClassAndName(child, className, tagName)) {
          if (newNodeName) {
            replaceByNewNode(child, newNodeValue, newNodeName); 
          }
          else {
            node.removeChild(child);
          }
        }
        else if (child && child.nodeType == 1) {
          removeDescendants(child, className, tagName, newNodeValue, newNodeName);
        }
      }
    }
  }
}


/*
 * Utility function to find a named item. The returned element
 * is equal to "form.elements[elem]" except that
 * it does not show some bugs as known from IE 5.5/6
 *
 * For multiple input elements with same name, an Array or NodeList of all 
 * elements is returned.
 *
 * @param form         HTML form object
 * @param elementName  name of desired input element
 * @return the input element found, or null
 */
function getFormElementByName(form, elementName) {
  if (form) {
    var elements = form.elements;
    var elem = elements[elementName];
    if (elem) return elem;
    // alert("element " + elementName + " not found, searching by index !!");
    var list = new Array();
    var len = (elements && elements.length) ? elements.length : 0;
    for (var i=0; i<len; i++) {
      if (elements[i].name == elementName) list[list.length] = elements[i];
    }
    if (list.length == 1) return list[0];
    else if (list.length > 1) return list;
  }
  return null;
}
 

/*
 * Utility function to display given form element
 */
function showForm(formName) {
  var form = document.forms[formName];
  if (form) {
    var msg = 'form ' + formName + ':';
    var elements = form.elements;
    var len = (elements && elements.length) ? elements.length : 0;
    for (var i=0; i<len; i++) {
      var elem = elements[i];
      var found = false;
      var found2 = false;
      if (form.elements[elem.name]) found = true;
      if (getFormElementByName(form, elem.name)) found2 = true;
      msg += '\n' + elem.name + ' = ' 
             + elem.value + ', found : ' + found + ' ' + found2;
    }
    alert(msg);
  }
}


/*
 * Checks, that this is the first form submit. If any form of
 * the current document was submitted before, an alert message
 * is displayed and false is returned. Otherwise the current
 * document is marked as submitted and the method returns true.
 *
 * The form should be submitted immediately afterwards, if true 
 * is returned.
 *
 * Usage:  <form onsubmit="return checkNotSubmitted(this, msg);" ...
 *
 * @param  form          HTML form object to be checked
 * @param  alertMessage  some optional alert message, displayed if the user tries to submit twice
 * @return 'true', if this is the first submit, otherwise 'false'
 */
function checkNotSubmitted(form, alertMessage) {
  if (form) {
    // check global 'submitted' property
    if (submitted) {
      if (alertMessage) alert(alertMessage);
      return false;
    }
    else {
      submitted = true;   // mark document as submitted
      return true;
    }
  }
}


/*
 * Checks, if a form can be submitted, but does not set submitted flag.
 * This method may be used at the beginning of any script, that may
 * or may not submit the form later.
 *
 * @param  form          HTML form object to be checked
 * @param  alertMessage  some optional alert message, displayed if the user tries to submit twice
 * @see checkNotSubmitted(form, alertMessage)
 * @see setSubmitted(form)
 */
function canSubmit(form, alertMessage) {
  if (form) {
    // check global 'submitted' property
    if (submitted) {
      if (alertMessage) alert(alertMessage);
      return false;
    }
    else {
      return true;
    }
  }
}


/*
 * DOM Moving Blocks
 * **********************************************************************
 *
 * All block-based copy, insert, delete and move functions asume some
 * HMTL element id to adress the block. This ID is NOT the identifier
 * of the outmost envelope of the block, but points to some inner payload
 * element.
 *
 * The outer block envelope is searched by className on the parent axis:
 *
 *  ---------------------------------------------------------------------
 * | Envelope, class = '<envelopeClass>'                                 |
 * |                                                                     |
 * |                                                                     |
 * |     ---------------------------------------------------             |
 * |    | Payload, id = '<payloadId>'                       |            |
 * |    |                                                   |            |
 * |    |                                                   |            |
 * |    |                                                   |            |
 * |    |---------------------------------------------------             |
 * |                                                                     |
 * |                                                                     |
 *  ---------------------------------------------------------------------
 *
 * This addressing schema is most flexible and avoids the use of an
 * additional ID for the envelope.
 */



/*
 * Finds the envelope block of the element with given id
 *
 * @param  payloadId  ID of payload element
 * @param  envelopeClass  CSS class of envelope, defaults to defaultEnvelopeClass
 * @return  the DOM node found, or null
 */
function findBlock(payloadId, envelopeClass) 
{
  if (payloadId) {
    var descendant = document.getElementById(payloadId);
    var className  = (envelopeClass) ? envelopeClass : defaultEnvelopeClass;
    return findAncestorOrSelf(descendant, className, null);
  }
  return null;
}


/*
 * Find the next envelope block in sibling list
 */
function findNextBlock(block, envelopeClass) 
{
  var className  = (envelopeClass) ? envelopeClass : defaultEnvelopeClass;
  return findNextSibling(block, className, null, false);
}


/*
 * Find the previous envelope block in sibling list
 */
function findPreviousBlock(block, envelopeClass) 
{
  var className  = (envelopeClass) ? envelopeClass : defaultEnvelopeClass;
  return findPreviousSibling(block, className, null, false);
}


/*
 * Insert a new block before the calling one. The new block
 * is copied from given template, where specified attribute
 * value is replaced.
 *
 * More special replacement actions must be done separately.
 *
 * @param callingId      payload ID of calling block
 * @param templateId     payload ID of template block, will be replaced in all attributes, if found
 * @param newId          payload ID of new block to be created
 * @param envelopeClass  CSS class of envelope, defaults to defaultEnvelopeClass
 * @param toggleId       id of some optional controll element, that should be toggled visible
 * @param pattern        optional matching pattern for additional attribute replacements, should be RegExp
 * @param newValue       replacement text
 * @return the new DOM element node
 */
function addBlockBefore(callingId, templateId, newId, envelopeClass, toggleId, pattern, newValue)
{
  if (toggleId) document.getElementById(toggleId).style.display="none";

  var callingBlock = findBlock(callingId, envelopeClass);
  var templateBlock = findBlock(templateId, envelopeClass);

  if (callingBlock && templateBlock)
  {
    // alert("addBlockBefore:" + "\ncalling = " + callingBlock + ", " + callingBlock.id + ", " + callingBlock.className + "\ntemplate = " + templateBlock + ", " + templateBlock.id + ", " + templateBlock.className);

    // copy template block
    var clone = templateBlock.cloneNode(true);
    // clone.style.display='none';

    // replace template ID and optional other attribute
    searchAndReplaceAttribute(clone, null, null, null, new RegExp('\\b' + templateId + '\\b'), newId);
    if (pattern && newValue) {
      var className  = (envelopeClass) ? envelopeClass : defaultEnvelopeClass;
      searchAndReplaceAttribute(clone, null, null, null, pattern, newValue);
    }

    // insert before calling block
    var parent = callingBlock.parentNode;
    clone = parent.insertBefore(clone, callingBlock);
    // clone.style.display='';
    return clone;
  }
  return null;
}


/*
 * Move block before previous one
 *
 * @param  payloadId      ID of payload element
 * @param  envelopeClass  CSS class of envelope, defaults to defaultEnvelopeClass
 */
function moveBlockUp(payloadId, envelopeClass)
{
  var block      = findBlock(payloadId, envelopeClass);
  if (!block) return;
  var otherBlock = findPreviousBlock(block, envelopeClass);

  // alert("moveBlockUp:" + "\nblock = " + block + ", " + block.id + ", " + block.className + "\nother = " + otherBlock + ", " + otherBlock.id + ", " + otherBlock.className);
  
  if ( otherBlock && (otherBlock.className.search(new RegExp("\\b"+unmovableClass+"\\b")) < 0) ) 
  {
    var parent   = block.parentNode;
    parent.removeChild(block);
    var newChild = parent.insertBefore(block, otherBlock);
  }
}


/*
 * Move block after next one
 *
 * @param  payloadId      ID of payload element
 * @param  envelopeClass  CSS class of envelope, defaults to defaultEnvelopeClass
 */
function moveBlockDown(payloadId, envelopeClass)
{
  var block      = findBlock(payloadId, envelopeClass);
  if (!block) return;
  var otherBlock = findNextBlock(block, envelopeClass);
  
  // alert("moveBlockUp:" + "\nblock = " + block + ", " + block.id + ", " + block.className + "\nother = " + otherBlock + ", " + otherBlock.id + ", " + otherBlock.className);
  
  if ( otherBlock && (otherBlock.className.search(new RegExp("\\b"+unmovableClass+"\\b")) <0) )
  {
    var parent   = block.parentNode;
    parent.removeChild(otherBlock);
    var newChild = parent.insertBefore(otherBlock, block);
  }
}


/*
 * Remove the envelope block of given payload
 *
 * @param  payloadId      ID of payload element
 * @param  envelopeClass  CSS class of envelope, defaults to defaultEnvelopeClass
 * @param  toggleId       id of some optional controll element, will be toggled visible
 */
function removeBlock(payloadId, envelopeClass, toggleId)
{
  if (toggleId) document.getElementById(toggleId).style.display="";

  var block = findBlock(payloadId, envelopeClass);
  if (block) removeNode(block);
}




/*
 * DOM events
 * **********************************************************************
 *
 * Utility classes for event handling
 */


/**
 * Extend form.onsubmit trigger with given function
 *
 * @param form       input form object
 * @param newFunctionBody  new JavaScript code to be executed (string)
 */
function addOnsubmitTrigger(form, newFunctionBody)
{
  var oldTrigger = form.onsubmit;
  // alert("old trigger : " + oldTrigger);

  var newTrigger = new Function("e", newFunctionBody);

  if (oldTrigger) {
    // return the value of the new Trigger or false, if any previous trigger has returned false
    form.onsubmit = function(e) { var oldVal = oldTrigger(e); var newVal = newTrigger(e); return (oldVal == false) ? false : newVal; };
  }
  else {
    form.onsubmit = function(e) { return newTrigger(e); }
  }
  // alert("new trigger : " + form.onsubmit);
}


/**
 * Extend onmousedown trigger with given function. Defaults to document.onmousedown.
 *
 * @param newFunctionBody  new JavaScript code to be executed (string)
 */
function addOnmousedownTrigger(domElement, newFunctionBody)
{
  if (!domElement) domElement = document;
  
  var oldTrigger = domElement.onmousedown;
  // alert("old trigger : " + oldTrigger);

  var newTrigger = new Function("e", newFunctionBody);

  if (oldTrigger) {
    // return the value of the new Trigger or false, if any previous trigger has returned false
    domElement.onmousedown = function(e) { var oldVal = oldTrigger(e); var newVal = newTrigger(e); return (oldVal == false) ? false : newVal; };
  }
  else {
    domElement.onmousedown = function(e) { return newTrigger(e); }
  }
  // alert("new trigger : " + domElement.onmousedown);
}


/**
 * Extend onmouseup trigger with given function. Defaults to document.onmouseup.
 *
 * @param newFunctionBody  new JavaScript code to be executed (string)
 */
function addOnmouseupTrigger(domElement, newFunctionBody)
{
  if (!domElement) domElement = document;
  
  var oldTrigger = domElement.onmouseup;
  // alert("old trigger : " + oldTrigger);

  var newTrigger = new Function("e", newFunctionBody);

  if (oldTrigger) {
    // return the value of the new Trigger or false, if any previous trigger has returned false
    domElement.onmouseup = function(e) { var oldVal = oldTrigger(e); var newVal = newTrigger(e); return (oldVal == false) ? false : newVal; };
  }
  else {
    domElement.onmouseup = function(e) { return newTrigger(e); }
  }
  // alert("new trigger : " + domElement.onmouseup);
}


/**
 * Extend onclick trigger with given function. Defaults to document.onclick.
 *
 * @param newFunctionBody  new JavaScript code to be executed (string)
 */
function addOnclickTrigger(domElement, newFunctionBody)
{
  if (!domElement) domElement = document;
  
  var oldTrigger = domElement.onclick;
  // alert("old trigger : " + oldTrigger);

  var newTrigger = new Function("e", newFunctionBody);

  if (oldTrigger) {
    // return the value of the new Trigger or false, if any previous trigger has returned false
    domElement.onclick = function(e) { var oldVal = oldTrigger(e); var newVal = newTrigger(e); return (oldVal == false) ? false : newVal; };
  }
  else {
    domElement.onclick = function(e) { return newTrigger(e); }
  }
  // alert("new trigger : " + domElement.onclick);
}


/**
 * Extend body.onload trigger with given function
 *
 * @param newFunctionBody  new JavaScript code to be executed (string)
 */
function addOnloadTrigger(newFunctionBody)
{
  // alert("document.body: " +  typeof(document.body) + "\nbody.onload: " + typeof(document.body.onload) + "\nwindow.onload: " + typeof(window.onload));

  // Mozilla uses window to store onload and onunload trigger,
  // Opera document.body and IE both
  var bodyObj = (document.all && document.body) ? document.body : window;

  var oldTrigger = bodyObj.onload;
  // alert("old trigger : " + oldTrigger);

  var newTrigger = new Function("e", newFunctionBody);
  // alert("New trigger : " + newTrigger);

  if (oldTrigger) {
    // return the value of the new Trigger or false, if any previous trigger has returned false
    bodyObj.onload = function(e) { var oldVal = oldTrigger(e); var newVal = newTrigger(e); return (oldVal == false) ? false : newVal; };
  }
  else {
    bodyObj.onload = function(e) { return newTrigger(e); }
  }
  // alert("new trigger : " + bodyObj.onload);
}


/**
 * Extend body.onunload trigger with given function
 *
 * @param newFunctionBody  new JavaScript code to be executed (string)
 */
function addOnunloadTrigger(newFunctionBody) 
{
  // alert("document.body: " +  typeof(document.body) + "\nbody.onunload: " + typeof(document.body.onunload) + "\nwindow.onunload: " + typeof(window.onunload));

  // Mozilla uses window to store onload and onunload trigger, Opera document.body
  // and IE both
  var bodyObj = (document.all && document.body) ? document.body : window;
  // alert("Body object: " + bodyObj);

  var oldTrigger = bodyObj.onunload;
  // alert("Old trigger : " + oldTrigger);

  var newTrigger = new Function("e", newFunctionBody);
  // alert("New trigger : " + newTrigger);

  if (oldTrigger) {
    // return the value of the new Trigger or false, if any previous trigger has returned false
    bodyObj.onunload = function(e) { var oldVal = oldTrigger(e); var newVal = newTrigger(e); return (oldVal == false) ? false : newVal; };
  }
  else {
    bodyObj.onunload = function(e) { return newTrigger(e); }
  }
  // alert("new trigger : " + bodyObj.onunload);
}


/*
 * Stop event bubbling up the ancestor axes
 */
function stopBubbling(e) 
{
  if (!e && window.event) var e = window.event;   // find event with IE

  // alert("e.stopPropagation: " + typeof(e.stopPropagation));

  // DOM Events L2, including NS7 + Opera 7
  if (e.stopPropagation) e.stopPropagation();

  // IE only
  else if (e.cancelBubble == false) e.cancelBubble = true; // IE 5.5
}


/*
 * Stop execution of the default action, e.g. scrolling after PGUP/PGDN
 * Must be used as return value !!
 */
function stopDefaultAction(e)
{
  if (!e && window.event) var e = window.event;   // find event with IE

  // alert("e.preventDefault: " + typeof(e.preventDefault));

  // DOM Events L2, including NS7 + Opera 7
  if (e && e.preventDefault) e.preventDefault();
  else if (e) { e.returnValue = false; }    // used by IE
  return false;     // needed by IE
}


/*
 * Helper function to get element target from event object. This method
 * reads the event target from given event object and finds the first HTML
 * element on the parent-or-self axes.
 *
 * @param e  the event object, used by NS7 and Mozilla
 */
function getTargetNode(e)
{
  var node;

  if (!e && window.event) var e = window.event;  // IE only

  if (e && e.srcElement)   // IE branch
  {
    node = e.srcElement;
    // alert("IE: exeption with node <" + node.nodeName + " id=" + node.id + ">\n " + node.innerHTML);
  }
  else if (e && e.target)  // Mozilla branch
  {
    node = e.target;
    while (node.parentNode && node.nodeType != node.ELEMENT_NODE) {
      node = node.parentNode;
    }
    // alert("DOM: exeption with node <" + node.nodeName + " id=" + node.id + ">\n " + node.innerHTML);
  }
  else 
  {
    alert("getTargetNode: unable to get node from event");
  }

  return node;
}


/**
 * Calculate relative X-Position for given event. The returned position
 * is relative to the DOM element triggering this event (see getTargetNode()).
 *
 * @param e  the event object, used by NS7 and Mozilla
 */
function xOffsetPosition(e)
{
  if (!e && window.event) var e = window.event;
  
  // alert( "xOffsetPosition: " + "\ne.x: " + e.x  + "\ne.offsetX: " + e.offsetX
  //   + "\ne.clientX: " + e.clientX + "\ne.layerX: " + e.layerX + "\ne.pageX: " + e.pageX);
    
  // IE/Opera: e.offsetX is position relative to DOM element triggering this event.
  // NS7/Mozilla: e.layerX (?)
  var x = (e && e.offsetX) ? e.offsetX : ((e && e.layerX) ? e.layerX : null);

  return x;
}


function yOffsetPosition(e)
{
  if (!e && window.event) var e = window.event;
  
  // alert( "yOffsetPosition: " + "\ne.y: " + e.y  + "\ne.offsetY: " + e.offsetY
  //   + "\ne.clientY: " + e.clientY + "\ne.layerY: " + e.layerY + "\ne.pageY: " + e.pageY);
    
  // IE/Opera: e.offsetX is position relative to DOM element triggering this event.
  // NS7/Mozilla: e.layerX (?)
  var y = (e && e.offsetY) ? e.offsetY : ((e && e.layerY) ? e.layerY : null);

  return y;
}


/**
 * Calculate absolute X-Position for given event. The returned position
 * is absolute in respect to the window content (body element).
 *
 * @param e  the event object, used by NS7 and Mozilla
 */
function xPosition(e) 
{
  if (!e && window.event) var e = window.event;

  var x = null;
  
  // IE: event.clientX is absolute position in window (DO NOT use event.x) !!
  // NS/Mozilla: e.pageX is absolute position in window content
  if (e && e.pageX)          // Mozilla etc
  {
    x =  e.pageX;
  }
  else if (e && e.clientX)   // IE etc
  {
    x = e.clientX;
    // IE calculates (clientX,clientY) relativ to window, not content. Fix this:
    // IE6: use 'document.documentElement' (=HTML element) instead of 'document.body' for scroll infos
    x += (document.documentElement && document.documentElement.scrollLeft) ? 
          document.documentElement.scrollLeft : document.body.scrollLeft;
  }

  return x;
}


/** see xPosition */
function yPosition(e) 
{
  if (!e && window.event) var e = window.event;

  var y = null;
  
  // IE: event.clientY is absolute position in window (DO NOT use event.y) !!
  // NS/Mozilla: e.pageY is absolute position in window content
  if (e && e.pageY)          // Mozilla etc
  {
    y =  e.pageY;
  }
  else if (e && e.clientY)   // IE etc
  {
    y = e.clientY;
    // IE calculates (clientX,clientY) relativ to window, not content. Fix this:
    // IE6: use 'document.documentElement' (=HTML element) instead of 'document.body' for scroll infos
    y += (document.documentElement && document.documentElement.scrollTop) ? 
          document.documentElement.scrollTop : document.body.scrollTop;
  }

  return y;
}


/*
 * DOM Measure
 * **********************************************************************
 *
 * Utility classes to retrieve the geometry and CSS properties of an element
 */


/**
 * Calculates inner height of browser window. This is highly 
 * browser dependend due to lack of any standard :-(
 *
 * Warning: the height of the document is in general larger than the window height,
 *          if scrollbars are visible
 *
 * See: http://www.howtocreate.co.uk/tutorials/index.php?tut=0&part=16
 *      http://msdn.microsoft.com/library/default.asp?url=/workshop/author/om/measuring.asp
 *
 *                           window.innerHeight        document.body.clientHeight    document.documentElement.clientHeight
 * =======================================================================================================================
 * Netscape 4 compatible     height of browser window  N/A                           N/A
 * Opera 6 and Netscape 6+   height of browser window  height of browser window(1)   N/A
 * Opera 7                   height of browser window  height of browser window(1)   height of document
 * Opera 9                   height of browser window  height of browser window(1)   height of document(3)
 * Moz 1.0 and Netscape 7    height of browser window  height of browser window(1)   N/A
 * Moz 1.5                   height of browser window  height of document(2)         height of browser window(1)
 * Fire Fox 1.5              height of browser window  height of document(2)         height of browser window(1)
 * IE 4 compatible           N/A                       height of browser window(1)   N/A
 * IE 5 compatible           N/A                       height of browser window(1)   0
 * IE 6 standards compliant  N/A                       height of document            height of browser window(1)
 *      mode compatible
 * Ice Browser               height of browser window  height of browser window      height of document
 * Others                    most give height of       most give height of           some give height of document
 *                           browser window            browser window
 *
 * (1) Not including scroll bar
 * (2) Strange: document.body.clientWidth = width of window excluding scoll bar!
 * (3) Strange: document.documentElement.clientWidth = width of window excluding scoll bar!
 *
 * @param win  the window to calculate the height of, optional
 */
function yWindowSize(win) {

  if (!win) win = window;
  var doc = win.document;

  var winHeight = 0;

  if (win.innerHeight) {                     // most non-IE
    winHeight = win.innerHeight;             // including scroll bar !!
    // if ( typeof(win.innerHeight) == 'number') winHeight = win.innerHeight - 20;  // remove some space for scroll bar
    if (doc.documentElement && doc.documentElement.clientHeight) {
      var docHeight  = doc.documentElement.clientHeight;
      var bodyHeight = doc.body.clientHeight;
      if (bodyHeight && navigator.userAgent.search(/\bOpera\b/) >= 0) winHeight = bodyHeight;  // mixed up in Opera, others?
      else                                                            winHeight = docHeight;
    }
    else {
      if (doc.body.clientHeight) winHeight = doc.body.clientHeight;   // window height without scrollbar
    }
  }
  else if (doc.documentElement && doc.documentElement.clientHeight) {
    winHeight = doc.documentElement.clientHeight;
  }
  else {
    winHeight = doc.body.clientHeight;
  }

  // alert( "Inner window height : " + winHeight 
  //      + "\nnavigator.userAgent: " + navigator.userAgent
  //      + "\nwindow.innerHeight = " + win.innerHeight 
  //      + "\ndoc.body.clientHeight = " + doc.body.clientHeight
  //      + "\ndoc.documentElement.clientHeight = " + doc.documentElement.clientHeight );

  return winHeight;
}


/**
 * Calculates inner width of browser window, see xWindowSize(win).
 */
function xWindowSize(win) {

  if (!win) win = window;
  var doc = win.document;

  var winWidth = 0;

  if (win.innerWidth) {                     // most non-IE
    winWidth = win.innerWidth;             // including scroll bar !!
    // if ( typeof(win.innerWidth) == 'number') winWidth = win.innerWidth - 20;  // remove some space for scroll bar
    if (doc.body.clientWidth) winWidth = doc.body.clientWidth;   // without scrollbar !?
  }
  else if (doc.documentElement && doc.documentElement.clientWidth) {
    winWidth = doc.documentElement.clientWidth;
  }
  else {
    winWidth = doc.body.clientWidth;
  }

  // alert( "Inner window width : " + winWidth 
  //      + "\nnavigator.userAgent: " + navigator.userAgent
  //      + "\nwindow.innerWidth = " + win.innerWidth 
  //      + "\ndoc.body.clientWidth = " + doc.body.clientWidth
  //      + "\ndoc.documentElement.clientWidth = " + doc.documentElement.clientWidth  );

  return winWidth;
}


/*
* Workaround for 100% height-problem
* 
* @param elementA Element to resize
* @param elementB Element to fit to
* @param sizeDecrease Number of pixels to substract from height of elementA because of headers and or footers
*/
function resizeToFitHeight(elementA, elementB, sizeDecrease) {
	var visibleHeight = yWindowSize();
	var elementASize = yElementSize(elementA);
       var elementBSize = yElementSize(elementB);

	// alert("leftcol: " + yElementSize(elementA) + " middlecol: " + yElementSize(elementB) + " visibleH: " + visibleHeight
       //  + " sizedec: " + sizeDecrease + " newSize: " + Math.max(visibleHeight - sizeDecrease, elementBSize) 
       //  );
       
       if(elementASize - sizeDecrease <= visibleHeight) {
         document.getElementById(elementA).style.height = Math.max(visibleHeight - sizeDecrease, elementBSize) + "px";
       }
}

function yElementSize(element) {
	var y, tmp;
	tmp = document.getElementById(element).style.pixelHeight;

	if(tmp) y = tmp;
	else y = document.getElementById(element).offsetHeight;
	
	return y;
}


/**
 * Utility function to remove given style property
 * @param elem  DOM element node
 * @param prop  the name of a property in CSS notation, e.g. "font-size"
 */
function removeStyle(elem, prop)
{
  if (!elem || !elem.style) return;
  var style = elem.style;
  
  // Check W3C DOM complience first
  if (style.removeProperty)                              // W3C DOM
  {
    style.removeProperty(prop);
  }

  // Proprietary stuff follows here
  else if (style.removeAttribute)                        // IE
  {
    // convert CSS style property name to Java-style, e.g. "fontSize"
    var ar = prop.match(/\w[^-]*/g);
    var s  = ar[0];
     
    for(var i = 1; i < ar.length; ++i)   
    {
      s += ar[i].replace(/\w/, ar[i].charAt(0).toUpperCase());
    }
    
    // alert(s + " : " + elem.currentStyle[s] + " (decl: " + elem.style[s] + ")");

    style.removeAttribute(s);
  }
  else 
  {
    if (window.isDebug) logMessage("No DOM interface found to remove style " + prop);
  }
}


/*
 * Retrieve requested style property from CURRENT CSS settings
 * @param elem  DOM element node
 * @param prop  the name of a property in CSS notation, e.g. "font-size"
 * @return  the current CSS value as string or null
 */
function getStyle(elem, prop)
{
  // Check W3C DOM complience first
  if( document.defaultView && document.defaultView.getComputedStyle )  // W3C DOM
  {
    return document.defaultView.getComputedStyle(elem, null).getPropertyValue(prop);
  }

  // Proprietary stuff follows here
  else if ( elem.currentStyle )                                         // IE code branch
  {  
    // convert CSS style property name to Java-style, e.g. "fontSize"
    var ar = prop.match(/\w[^-]*/g);
    var s  = ar[0];
     
    for(var i = 1; i < ar.length; ++i)   
    {
      s += ar[i].replace(/\w/, ar[i].charAt(0).toUpperCase());
    }
    
    // alert(s + " : " + elem.currentStyle[s] + " (decl: " + elem.style[s] + ")");

    return elem.currentStyle[s];
  }
  
  // if all fails, return nothing :-(
  return null;
}


/*
 * Retrieves current pixel value of given CSS property
 * @param elem  DOM element node
 * @param prop  the name of a property in CSS notation, e.g. "font-size"
 * @return  the current CSS value in pixels or null
 */
function getPixelStyle(elem, prop) {
  
  var value = getStyle(elem, prop);

  if (value) {
    // alert(value);
    var idx = value.indexOf('px');
    if (idx >= 0) {
      return parseInt(value.substring(0, idx));
    }
  }
  return null;
}


/*
 * Calculate the width induced by any child node. The node
 * itself is not tested.
 *
 * @param node     some DOM element node
 * @param refNode  DOM element node, must be offsetParent of 
 *                 given node and all of its children
 * @todo correct for border width in Mozilla
 */
function inducedChildWidth(node, refNode) 
{
  var width = 0;

  // alert("Start inducedChildWidth: " + node.tagName + (node.className ? "."+node.className : "") + (node.id? "#"+node.id : ""));

  var childNodes = node.childNodes;
  if (childNodes) for (var i=0; i<childNodes.length; i++)
  {
    var child = childNodes[i];
    if (child.nodeType == 1 && child.offsetWidth && child.offsetParent == refNode)
    {
      // TODO respect clientWidth if available with Mozilla
      width = Math.max(width, child.offsetLeft + child.offsetWidth);
 
      // check child width for DIV boxes (Mozilla bug)
      if (child.tagName == 'DIV' || child.tagName == 'FORM') width = Math.max( width, inducedChildWidth(child, refNode) );
    }
  }

  // alert( "End inducedChildWidth: " + node.tagName + (node.className ? "."+node.className : "") + (node.id? "#"+node.id : "")
  //     + "\noffsetLeft: " + node.offsetLeft + "\noffsetWidth: " + node.offsetWidth + "\nchild width: " + width );

  return width;
}


/*
 * Displays font-size settings of given DOM node - used in debugging only
 */
function showFontSize(elem)
{
  // var elem  = document.getElementById("ioPortletHead0");

  // read declared CSS style
  var decl = elem.style;

  // read current CSS style
  var comp = window.getComputedStyle ? window.getComputedStyle(elem, null) : elem.currentStyle;

  alert("Tag : " + elem.tagName + ", class : " + elem.className);
  alert("Declared: font-size : " + decl.fontSize + ", height : " + decl.height);

  if (window.getComputedStyle) {  // NS 7 branch
    // shorthand properties are not supported by NS 7.0:
    // alert("Computed: font-size : " + comp.fontSize + ", height : " + comp.height);
    alert("Computed: font-size : " + comp.getPropertyValue("font-size") + ", height : " + comp.getPropertyValue("height"));

    // use CSSPropertyValue to convert show in px and pt:
    var size = comp.getPropertyCSSValue("font-size");
    alert("Font-Size: type : " + size.primitiveType + ", size : " + size.getFloatValue(5) + "px = " + size.getFloatValue(9) + "pt");
  }
  else {                         // IE 5.5 branch
    alert("Computed: font-size : " + comp.fontSize + ", height : " + comp.height);
  }
}


/*
 * Returns string with nodeName, nodeType, className and ID
 */
function getNodeDescription(node) {
  if (node && node.nodeType == 1) {
    return node.nodeName + (node.className ? "."+node.className : "") + (node.id? "#"+node.id : "");
  }
  else if (node && node.nodeType == 3) {
    return "Text:" + node.nodeValue;
  }
  else if (node) {
    "Unknown type: " + node.nodeType;
  }
  return "" + node;
}


/*
 * Show some node properties - used in debugging only
 */
function showNode(node, title) {
  var msg;
  if (!node) return;
  if (node.nodeType == 1) {         // element node
    // msg = node.tagName + (node.className ? "." + node.className : "") + (node.id ? "#" + node.id : "") + "\n" + printDOM(node);
    msg = node.tagName + (node.className ? "." + node.className : "") + (node.id ? "#" + node.id : "") + "\n" + node.innerHTML;
  } else if (node.nodeType == 3) {  // text node
    msg = "Text:\n" + node.nodeValue;
  }
  else {
    msg = "Unknown type: " + node.nodeType;
  }
  if (title) alert(title + "\n" + msg);
  else alert(msg);
}


/*
 * Show DOM and CSS properties of given DOM node - used in debugging only
 */
function showNodeProperties(elem, title) 
{
  alert((title ? title + "\n" : "") + elem.tagName + (elem.className ? "."+elem.className : "") + (elem.id? "#"+elem.id : "")
        + "\n"
        + "\n offsetWidth  : " + elem.offsetWidth    + " offsetLeft  : " + elem.offsetLeft 
        + "\n offsetHeight : " + elem.offsetHeight   + " offsetTop   : " + elem.offsetTop
        + "\n clientWidth  : " + elem.clientWidth    + " clientLeft  : " + elem.clientLeft 
        + "\n clientHeight : " + elem.clientHeight   + " clientTop   : " + elem.clientTop
        + "\n"
        + "\n document.getElementById (W3C) : " + (document.getElementById ? "ok" : '-')
        + "\n document.all (IE)             : " + (document.all ? "ok" : '-')
        + "\n document.defaultView (W3C)    : " + (document.defaultView && document.defaultView.getComputedStyle ? "ok" : '-')
        + "\n elem.currentStyle (IE)        : " + (elem.currentStyle ? "ok" : '-')
        + "\n"
        + "\n width        : " + getStyle(elem, "width")      + " (decl: " + elem.style.width + ")"
        + "\n height       : " + getStyle(elem, "height")     + " (decl: " + elem.style.height + ")"
        + "\n top          : " + getStyle(elem, "top")        + " (decl: " + elem.style.top + ")"
        + "\n right        : " + getStyle(elem, "right")      + " (decl: " + elem.style.right + ")"
        + "\n bottom       : " + getStyle(elem, "bottom")     + " (decl: " + elem.style.bottom + ")"
        + "\n left         : " + getStyle(elem, "left")       + " (decl: " + elem.style.left + ")"
        + "\n"
        + "\n position     : " + getStyle(elem, "position")   + " (decl: " + elem.style.position + ")"
        + "\n display      : " + getStyle(elem, "display")    + " (decl: " + elem.style.display + ")"
        + "\n visibility   : " + getStyle(elem, "visibility") + " (decl: " + elem.style.visibility + ")"
        + "\n overflow     : " + getStyle(elem, "overflow")   + " (decl: " + elem.style.overflow + ")"
        + "\n"
        + "\n clip         : " + getStyle(elem, "clip")       + " (decl: " + elem.style.clip + ")"
        + "\n clip-top     : " + getStyle(elem, "clip-top")   + " (decl: " + elem.style.clipTop + ")"
        + "\n clip-right   : " + getStyle(elem, "clip-right") + " (decl: " + elem.style.clipRight + ")"
        + "\n clip-bottom  : " + getStyle(elem, "clip-bottom")+ " (decl: " + elem.style.clipBottom + ")"
        + "\n clip-left    : " + getStyle(elem, "clip-left")  + " (decl: " + elem.style.clipLeft + ")"
        + "\n border-top   : " + getStyle(elem, "border-top-width")   + " (decl: " + elem.style.borderTopWidth + ")"
        + "\n border-right : " + getStyle(elem, "border-right-width") + " (decl: " + elem.style.borderRightWidth + ")"
        + "\n border-bottom: " + getStyle(elem, "border-bottom-width")+ " (decl: " + elem.style.borderBottomWidth + ")"
        + "\n border-left  : " + getStyle(elem, "border-left-width")  + " (decl: " + elem.style.borderLeftWidth + ")"
  );
}


/*
 * Print DOM tree starting with given node
 * @param node   some start node
 * @param prefix line prefix, needed for nice formatting
 * @return the nicely formatted dom tree with given start node
 */
function printDOM(node, prefix) {
  var tree = "";
  if (!prefix) prefix = "";
  if (node.nodeType == 1) {         // element node
    var nsprefix = (node.scopeName && node.scopeName != 'HTML') ? node.scopeName : null;  // IE only
    var nsURL    = (node.tagUrn) ? node.tagUrn : null;  // IE only
    tree += prefix + ((nsprefix) ? nsprefix+":" : "")
                   + ((node.tagName) ? node.tagName : "??")
                   + ((nsURL) ? "("+nsURL+")" : "")
                   + ((node.className) ? "." + node.className : "")
                   + ((node.id) ? "#" + node.id : "");
    var attributes = node.attributes;
    var attrDesc = "";
    for (var i=0; i<attributes.length; i++) {
      var name = attributes[i].nodeName;
      var value = attributes[i].nodeValue;
      // Attention: IE stores all known attribute, value may be null or '' !!
      if (name == 'class' || name == 'id' || value == null || value == '') continue;
      if (attrDesc.length > 0) attrDesc += ", ";
      attrDesc += name + "=" + value;
    }
    tree += ( (attrDesc.length > 0) ? "[" + attrDesc + "]" : "" ) + "\n";
    var childPrefix = (prefix) ? prefix + "- " : "- ";
    var childNodes = node.childNodes;
    for (var i=0; i<childNodes.length; i++) {
      tree += printDOM(childNodes[i], childPrefix);
    }
  } 
  else if (node.nodeType == 3) {  // text node
    tree += prefix + "#Text: >" + node.nodeValue + "<\n";
  }
  else {
    tree += prefix + "Unknown type: " + node.nodeType + "\n";
  }
  return tree;
}


/**
 * Opens browser window and display DOM tree starting with given root node
 *
 * @param  winName  name of window object, used during window fethc or creation
 * @param  node     DOM node, root of tree to be shown
 */
function showDomTree(winName, node) 
{
  // find window - or create new one
  var domTreeWindow = window.open("", winName, 
        "width=800,height=600,status=no,scrollbars,resizable,dependent,top=200,left=200");
  domTreeWindow.document.open();
  domTreeWindow.document.write("<html lang='en'><body style='font-size:8pt'><pre>");
  domTreeWindow.document.write(printDOM(node));
  domTreeWindow.document.write("</pre></body></html>");
  domTreeWindow.document.close();
}

