/** basic.js
 * @autor: janne louw
 * @date: 30-11-2005
 *
 * @description: basic js library
 */
 
/** prototype Map (replaces Object)
 * 
 */
function Map( obj ) {
   var key;
   for (key in obj) {
      this[key] = obj[ key ];   
   }
}

/* Global map. General good practice to add all globals to this collection instead of plain. */
var Global = new Map();

var Globals = Global;

/* ************************************************************************************************** */
/* --------------------------- test functions ------------------------------------------------------- */

/** isUndefined
 * returns true ig the value is undefined, NaN or equivalent
 */
function isUndefined(value) {
   switch (typeof value) {
      case 'undefined':
         return true;
      case 'number':
         return isNaN(value);
      case 'object':
         return String(value) == 'undefined';
   }
   return false;
}
/** isEmpty
 * returns true if value is undefined, null, an empty string, 
 * a string containing only whintespace characters, an empty array or en empty map.
 */
function isEmpty(value) {
   switch (typeof value) {
      case 'undefined':
         return true;
      case 'number':
         return isNaN(value);
      case 'string':
         return value.trim().length == 0;
      case 'object':
         if (value === null) {
            return true;
         }
         if (value instanceof Array) {
            return value.length == 0;
         }
         if (value instanceof Map) {
            for (key in value) {
               return false;
            }
            return true;
         }
         return String(value) == 'undefined';         
   }
   return false;
   
}
/** isElement
 * returns true if nade is an element
 */
function isElement(node) {
   return (typeof node == 'object' && node.nodeType == 1);
}
/** isUsefulNode
 * returns false if node is an empty text node or a comment
 */
function isUsefulNode(node) {
   //8 is comment, 3 is text node
   return node.nodeType != 8 && !(node.nodeType == 3 && isEmpty(node.nodeValue));
}

/** isPositionElement
 * returns true if node is an element that affects absolute positioning
 */
function isPositionElement(node) {
   if (!isElement(node)) {
      return false
   }
   switch (getStyleValue(node, 'position')) {
      case 'absolute':
      case 'relative':
      case 'fixed':
         return true;
   }
   return false;
}

/** isMap
 * returns true if value is an instance of Map
 */
function isMap(value) {
   return typeof value == 'object' && value instanceof Map;
}
/** isValidEmail
 * returns true if the string
 */
function isValidEmail(string) {
   var re = /^[-_.\w]+@((([\w]|[\w][\w-]*[\w])\.)+(ad|ae|aero|af|ag|ai|al|am|an|ao|aq|ar|arpa|as|at|au|aw|az|ba|bb|bd|be|bf|bg|bh|bi|biz|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|com|coop|cr|cs|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|edu|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gh|gi|gl|gm|gn|gov|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|in|info|int|io|iq|ir|is|it|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|mg|mh|mil|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|museum|mv|mw|mx|my|mz|na|name|nc|ne|net|nf|ng|ni|nl|no|np|nr|nt|nu|nz|om|org|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|pro|ps|pt|pw|py|qa|re|ro|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw)|(([0-9][0-9]?|[0-1][0-9][0-9]|[2][0-4][0-9]|[2][5][0-5])\\.){3}([0-9][0-9]?|[0-1][0-9][0-9]|[2][0-4][0-9]|[2][5][0-5]))$/i;
   return re.test(string);
}
/* ************************************************************************************************** */
/* --------------------------- parse functions ------------------------------------------------------ */

/** parseElem
 * returns the value if it's an element, the document.getElementById if it's a string, 
 * otherwise returns null
 */
function parseElem(value) {
   if (typeof value == 'string') {
      return elemById(value);
   }
   if (!isElement(value)) {
      return null;
   }
   return value;
}
/** parseElem
 * returns the value if it's a useful node, the document.getElementById if it's a string, 
 * otherwise returns null
 */
function parseNode(value) {
   if (typeof value == 'string') {
      value = elemById(value);
   }
   if (!isUsefulNode(value)) {
      return null;
   }
   return value;
}
/* ************************************************************************************************** */
/* --------------------------- find elements / nodes ------------------------------------------------ */


/** elemById
 * returns the element with id id, or null if it is not found;
 */
function elemById(id) {
   if (document.getElementById) {
      return document.getElementById(id);
   }
   else if (document.all) {
      return document.all[id];
   }
   else if (document.layers) {
      return document.layers[id];
   }
}
/** elemBody
 * returns the body element
 */
function elemBody() {
   return elemsByTag('body', document)[0];
}
/** elemsByTagName
 * returns an array of elements with given tagname
 * if a parent is provided only return the child elements with tagname
 */
function elemsByTag(strTagname, elemParent) {
   var arrElems, listElems, i;

   if (isUndefined(elemParent)) {
      elemParent = document;
   }
   
   listElems = elemParent.getElementsByTagName(strTagname);
   
   //convert to Array, because DOM method returns nodeList, not Array.
   arrElems = new Array(); 
   for (i = 0; i < listElems.length; ++i ) {
      arrElems[ arrElems.length ] = listElems[i];
   }
   return arrElems;
}
/** elemsChilds
 * returns an array of child elements
 */
function elemsChilds(elemParent) {
  var listChilds, arrChilds, i;
  
  listChilds = elemParent.childNodes;
  
  arrChilds = new Array();
  for (i = 0; i < listChilds.length; i++) {
     if (isElement( listChilds[i] )) {
        arrChilds[ arrChilds.length ] = listChilds[i];
     }
  }
  return arrChilds;
}
/** elemsAllChilds
 * returns an array of all elements in the root under the argument element
 */
function elemsAllChilds(elemRoot) {
   var arrResult;

   arrResult = new Array();

   recursiveAllChilds(elemRoot);
   return arrResult;
   
   function recursiveAllChilds(elemRoot) {
      var i, arrChilds;
      arrResult[ arrResult.length ] = elemRoot;
      
      var arrChilds = elemsChilds(elemRoot);
      
      for (i = 0; i < arrChilds.length; i++) {
         recursiveAllChilds(arrChilds[i]);
      }
   }
}
function elemsByClass(strClassName, elemParent) {
   var regExp, arrClassNodes, arrAllNodes, i;

   if (isUndefined(elemParent)) {
      elemParent = document;
   }

   regExp = new RegExp("\\b" + strClassName + "\\b", '');
   arrAllNodes = elemsAllChilds(elemParent);

   arrClassNodes = new Array();

   for (i = 0; i < arrAllNodes.length ; ++i ) {
      
      if ( !isElement(arrAllNodes[i]) ) {
         continue;
      }
      if ( regExp.test( String(arrAllNodes[i].className) ) ) {
         arrClassNodes[ arrClassNodes.length ] = arrAllNodes[i];
      }
   }
   return arrClassNodes;
}
function elemsAncestors(node) {
   var arrAncestors = new Array(node);

   while (node.parentNode) {
      node = node.parentNode;
      arrAncestors[ arrAncestors.length ] = node;
   }
   arrAncestors.reverse();
   return arrAncestors;
}

function nodeNextSibling(node) {
   while (node.nextSibling && !isUsefulNode(node.nextSibling)) {
      node = node.nextSibling;
   }
   return node.nextSibling;
}
function nodePreviousSibling(node) {
   while (node.previousSibling && !isUsefulNode(node.previousSibling)) {
      node = node.previousSibling;
   }
   return node.previousSibling;
}


/* ************************************************************************************************** */
/* ---------------------- advanced get property functions ------------------------------------------- */

// credit: functions getTop() and getLeft() graciously borrowed and edited from http://www.quirksmode.org/
var RELATIVE = true;
/** getTop
 * returns a map containing the distance of element to the top and left of the document
 * if flag RELATIVE is set, use containing position element rather then the document
 */
function getPosition(value, boolRelative) {
   var elem, curtop, curleft;
   elem = parseElem(value);
   
   curtop = 0;
   curleft = 0;
   if (elem.offsetParent) {
      while (elem.offsetParent && !(boolRelative && isPositionElement(elem) )) {
         curleft += elem.offsetLeft;
         curtop += elem.offsetTop;
         elem = elem.offsetParent;
      }
   }
   else if (elem.y) {
      curleft += elem.x;
      curtop += elem.y;
   }
   return new Map({top: curtop, left: curleft});
}

// Many thanks to Peter-Paul Koch of quirksmode.org for doing my dirty work.
/** measure
 *in: string 'window' | 'document' | 'scroll'
 *out: obj with .x and .y for width and height
 */
function measure(part) {
   var mapSize = new Map();
   var x, y, test1, test2;

   switch (part) {
      case 'window':
         //viewport size
         if (self.innerHeight) { 
            // all except Explorer
            x = self.innerWidth;
            y = self.innerHeight;
         }
         else if (document.documentElement && document.documentElement.clientHeight) {
            // Explorer 6 Strict
            x = document.documentElement.clientWidth;
            y = document.documentElement.clientHeight;
         }
         else if (document.body) {
            // other Explorers
            x = document.body.clientWidth;
            y = document.body.clientHeight;
         }
         break;
      case 'scroll':
         // pixels scrolled
         if (self.pageYOffset) {
            // all except Explorer
            x = self.pageXOffset;
            y = self.pageYOffset;
         }
         else if (document.documentElement && document.documentElement.scrollTop) {
            // Explorer 6 Strict
            x = document.documentElement.scrollLeft;
            y = document.documentElement.scrollTop;
         }
         else if (document.body) {
            // all other Explorers
            x = document.body.scrollLeft;
            y = document.body.scrollTop;
         }
         break;
      case 'document':
         //total doc height
         test1 = document.body.scrollHeight;
         test2 = document.body.offsetHeight
         if (test1 > test2) {
            // all but Explorer Mac
            x = document.body.scrollWidth;
            y = document.body.scrollHeight;
         }
         else {
            // Explorer Mac;
            //would also work in Explorer 6 Strict, Mozilla and Safari
            x = document.body.offsetWidth;
            y = document.body.offsetHeight;
         }
         break;
   }

   mapSize.x = x;
   mapSize.y = y;

   return mapSize;
}

var NOUNIT = true;
/** getStyleValue
 * in: idstring or objectelement, string css-syntax property, [ bool nounit-switch]
 * out: string CSS-value || int CSS-value
 * The nounit boolean converts the value of a property to a number, eg '15px' -> 15
 */
function getStyleValue(value, property, boolNoUnit) {
   var elem, styleValue, cssSyntax, jsSyntax = '', i, unitfree;
   elem = parseElem(value);

   if (document.defaultView && document.defaultView.getComputedStyle) {
      //if Moz/Opera/Safari, just get the style:
      styleValue = document.defaultView.getComputedStyle(elem,null).getPropertyValue(property);
   }
   else if (elem.currentStyle) {
      //if IE, rewrite property and get the style.
      while (property.indexOf('-') > -1)
      {
         _index = property.indexOf('-');

         jsSyntax += property.substr(0, _index);
         jsSyntax += property.substr(_index+1, 1).toUpperCase();
         property = property.substr(_index+2);
      }
      jsSyntax += property;

      styleValue = elem.currentStyle[jsSyntax];
   }

   if (boolNoUnit) {
      styleValue = parseFloat(styleValue);
      if (isNaN(styleValue)) {
         return 0;
      }
   }
   return styleValue;
}

//obtain depth in tree. Takes object.
function getLevel(elem) {
   var levelCount = 0;
   while (elem = elem.parentNode) {
      levelCount++;
   }
   return levelCount;
}

/* ************************************************************************************************** */
/* ---------------------- advanced set property functions ------------------------------------------- */

/** addLoadEvent
 * appends the argument function to the onload events. previously added functions are
 * not overwritten.
 */
function addLoadEvent(funcNew) {
  var funcOld = window.onload;
  if (typeof window.onload != 'function') {
    window.onload = funcNew;
  } 
  else {
    window.onload = function() {
      if (funcOld) {
        funcOld();
      }
      funcNew();
    }
  }
}

/** toggle
 * changes the display of the element to 'none' if it is not, and to newstate if it is 'none'
 * default value for newstate is 'block'
 * returns true if the element is in newstate afterwards
 */
function toggle(value, newstate) {
   var elem, boolDisplayState;
   if (isUndefined(newstate)) {
      newstate = 'block';
   }
   elem = parseElem(value);

   boolDisplayState = getStyleValue(elem,'display') == 'none';

   if (boolDisplayState) {
      elem.style.display = newstate;
   }
   else {
      elem.style.display = 'none';
   }
   return boolDisplayState;
}


//event assignment. e.g.: listen(window,'load',prepLayout);
function listen(elem, event, func) {
   if (elem.addEventListener) {
      elem.addEventListener(event,func,false);
   }
   else if (elem.attachEvent) {
      elem.attachEvent('on'+event,func);
   }
   else {
      elem['on' + event] = func;
   }
}
function deafen(elem, event, func) {
   if (elem.removeEventListener) {
      elem.removeEventListener(event,func,false);
   }
   else if (elem.detachEvent) {
      elem.detachEvent('on'+event,func);
   }
   else {
      elem['on' + event] = null;
   }
}

function addLoadPngFix() {
   //The PNG hack for IE 6.
   if (/MSIE (6\.\d+)/.test(navigator.appVersion))
   {
      addLoadEvent( 
         function () {
            var _i, arrImgTags;
            arrImgTags = elemsByTag('img');
            for (_i = 0; _i < arrImgTags.length; ++_i) {
               if (/\.png$/i.test(arrImgTags[_i].src)) {
                  arrImgTags[_i].style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + arrImgTags[_i].src + "', sizingMethod='scale')";
                  arrImgTags[_i].style.width = arrImgTags[_i].width + 'px';
                  arrImgTags[_i].style.height = arrImgTags[_i].height + 'px';
                  arrImgTags[_i].src = '/public/bin/gfx/transparent.gif';
               }
            }
         }
      );
   }
}
/* ************************************************************************************************** */
/* ---------------------- form functions ------------------------------------------------------------ */

function submit(strFormId, strSubmitId) {
   elemById('submitid').value = strSubmitId;
   elemByIf(strFormId).submit();
}

/* ************************************************************************************************** */
/* --------------------- element existence manipulation functions ----------------------------------- */

function spawnElem(tagname) {
  return document.createElement(tagname);
}

function spawnText(content) {
  return document.createTextNode(content);
}

//advanced spawn: specify the parent object, the tag to spawn, and the contents of the spawned element
function spawnAndAdd(parent,elem,content) {
  var newElem = spawnElem(elem);
  
  if (!isUndefined(content)) {
     newElem.appendChild( spawnText(content) );
  }
  
  parent.appendChild(newElem);

  return newElem;
}

/** spawnBlock
 * creates a div in the root and returns it.
 */
function spawnBlock(id , className , content) {
   var newElem;
   if (isUndefined(content)) {
      content = '';
   }
   if (isUndefined(className)) {
      className = '';
   }
   
   newElem = spawnAndAdd( document.body, 'div', content );
   newElem.id = id;
   newElem.className = className;

   return newElem;
}

//removes a node and returns it. Yes, that's possible.
function removeNode(node) {
   return node.parentNode.removeChild(node);
}

/* ************************************************************************************************** */
/* --------------------------- general useful functions --------------------------------------------- */
function mapToString(map, indent) {
   var string, key, spaces, i;
   spaces = '';
   if (isUndefined(indent)) { indent = 0; }
   for (i = 0; i < indent; ++i) {
      spaces += ' ';
   }
   
   string = spaces + '{\n' + spaces;
   for (key in map) {
      string += key + ':';
      switch (typeof map[key]) {
         case 'undefined':
            string += '[undefined]';
            break;
         case 'function':
            string += '[function]';
            break;
         case 'object':
            if (map[key] == null) {
               string += '[null]';
            }
            else if (map[key] instanceof Map) {
               string += mapToString( map[key], indent + 5 );
            }
            else if ('toString' in map[key] && typeof map[key].toString == 'function') {
               string += map[key].toString();
            }
            else {
               string += '[object]';
            }
            break;
         default:
            string += map[key];
      }
      string += ',\n' + spaces;
   }
   string += '}\n';
   return string;
}

/* function outputBox
 * creates a small div in BODY in the top left corner, with methods to dump output into it.
 * useful as an output "console"
 * the optional leftCoord arg shifts it to the right by (leftCoord)px -- handy if you need to to be out of the way.
 * Methods:
 *  set: add a single string + <br> in the box
 *  setAll: replaces contents of box with string
 *  escaped: adds text to the box; escapes HTML tags and performs rudimentary formatting.
*/
function outputBox(leftCoord) {
   if (!leftCoord) {
      leftCoord = 2;
   }

   var box = spawnBlock('bugbox');

   box.style.position = 'absolute';
   box.style.top = '2px';
   box.style.left = leftCoord  + 'px';
   box.style.backgroundColor = '#ffffff';
   box.style.padding = '3px';
   box.style.zIndex = '999';
   box.style.color = 'black';
   box.style.fontFamily = 'Courier New';
   box.style.fontSize = '11px';
   box.set = function (str) { this.innerHTML += str+'<br />';};
   box.setAll = function (str) { this.innerHTML = str+'<br />';};
   box.escaped = function (str) { this.innerHTML = str.replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\n([\t\s]*)/g,'<br />$1') + '<br />';};

   return box;
}
/* function writeSafeMailto and execSafeMailto
 * writes the provided email adress so that email spiders cannot detect it
 * the "@" in the adress has to be replaced by "!at!"
 * on click it will have the same effect as a standard mailto: link, with the email address appearing normally
 */
function writeSafeMailto(strEmail) {
   var intAtPos = strEmail.indexOf('!at!');
   var strUser = strEmail.substr(0,intAtPos);
   var strDom = strEmail.substr(intAtPos + 4);
   var strStyle = 'style="text-decoration: underline;cursor: pointer;" ';
   var strOutput = '<a ' + strStyle + 'class="safemail" onclick="execSafeMailto(\'' + strEmail + '\');">';
   strOutput += strUser + '<img src="/public/bin/gfx/at.gif" alt="@" style="margin-bottom: -2px;"/>' + strDom + '</a>';
   document.write(strOutput);
}
function execSafeMailto(strEmail) {
   var strMailto = 'mailto:' + strEmail.replace('!at!', '@');
   //alert('opening mailto window, then attempt to close: ' + strMailto);
   var objWin = window.open(strMailto,'emailWindow');
   if (objWin && objWin.open &&!objWin.closed) objWin.close();
}
