
/* core.js, WAJAF, the WebAbility(r) Javascript Application Framework
   Contains multi purpose functions, browser and WA objects
   (c) 2008 Philippe Thomassigny
*/

// The context is used ONLY if there is no '-' into the element name.
var domContext = '';
function getDomNode(domID)
{
  if (arguments.length > 1)
  {
    var elements = [];
    for (var i = 0, l = arguments.length; i < l; i++)
      elements.push(getDomNode(arguments[i]));
    return elements;
  }
  if (typeof domID == 'string')
  {
    if (domID.indexOf('-')==-1)
      return document.getElementById(domContext + domID) || document.getElementById(domID);
    return document.getElementById(domID);
  }
  return null;
}

/* ALIAS to be compatible with Prototype @ www.conio.net */
var $ = getDomNode;

var _4glContext = '';
function get_4glNode(ID)
{
  if (arguments.length > 1)
  {
    var elements = [];
    for (var i = 0, l = arguments.length; i < l; i++)
      elements.push(get4glNode(arguments[i]));
    return elements;
  }
  if (typeof ID == 'string')
  {
    if (ID.indexOf('-')==-1)
      return wa4glManager.getNode(_4glContext + ID) || wa4glManager.getNode(ID);
    return wa4glManager.getNode(ID);
  }
  return null;
}

/* ALIAS for fast use */
var $N = get_4glNode;

function getiApplication(ID)
{
  if (arguments.length > 1)
  {
    var elements = [];
    for (var i = 0, l = arguments.length; i < l; i++)
      elements.push(getiApplication(arguments[i]));
    return elements;
  }
  var node = $N(ID);
  if (node)
    return node.iapplication;
  return null;
}

/* ALIAS for fast use */
var $A = getiApplication;

function getiContainer(ID)
{
  if (arguments.length > 1)
  {
    var elements = [];
    for (var i = 0, l = arguments.length; i < l; i++)
      elements.push(getiContainer(arguments[i]));
    return elements;
  }
  var node = $N(ID);
  if (node)
    return node.icontainer;
  return null;
}

/* ALIAS for fast use */
var $C = getiContainer;

function getiElement(ID)
{
  if (arguments.length > 1)
  {
    var elements = [];
    for (var i = 0, l = arguments.length; i < l; i++)
      elements.push(getiElement(arguments[i]));
    return elements;
  }
  var node = $N(ID);
  if (node)
    return node.ielement;
  return null;
}

/* ALIAS for fast use */
var $E = getiElement;

// Date validation function
function isDate(year, month, day)
{
  var numdays = [,31,28,31,30,31,30,31,31,30,31,30,31][month];
  return day>0 && !!numdays && (day<=numdays || day==29 && year%4==0 && (year%100!=0 || year%400==0) )
}

// Hour validation function
function isHour(hour, min, sec)
{
  return hour>=0 && hour<=23 && min>=0 && min<=59 && sec>=0 && sec<=59;
}

// UTF-8 conversions, encoding
function utf8_encode(value)
{
  if (typeof value == 'object')
  {
    var elements = {};
    for (var i in value)
      elements[i] = utf8_encode(value[i]);
    return elements;
  }
  value = value.replace(/\r\n/g,"\n");
  var utf = "";
  for (var i = 0, l = value.length; i < l; i++)
  {
    var c = value.charCodeAt(i);
    if (c < 128)
    {
      utf += String.fromCharCode(c);
    }
    else if((c > 127) && (c < 2048))
    {
      utf += String.fromCharCode((c >> 6) | 192);
      utf += String.fromCharCode((c & 63) | 128);
    }
    else
    {
      utf += String.fromCharCode((c >> 12) | 224);
      utf += String.fromCharCode(((c >> 6) & 63) | 128);
      utf += String.fromCharCode((c & 63) | 128);
    }
  }
  return utf;
}

// public method for utf8 decoding
function utf8_decode(value)
{
  var str = "";
  var i = 0;
  var c1 = c2 = c3 = 0;
  while ( i < value.length )
  {
    c1 = value.charCodeAt(i);
    if (c1 < 128)
    {
      str += String.fromCharCode(c1);
      i++;
    }
    else if((c1 > 191) && (c1 < 224))
    {
      c2 = value.charCodeAt(i+1);
      str += String.fromCharCode(((c1 & 31) << 6) | (c2 & 63));
      i += 2;
    }
    else
    {
      c2 = value.charCodeAt(i+1);
      c3 = value.charCodeAt(i+2);
      str += String.fromCharCode(((c1 & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
      i += 3;
    }
  }
  return str;
}

// compatibility old wa3
var utf8 = {};
utf8.encode = utf8_encode;
utf8.decode = utf8_decode;

// color transfer
function RGBColor(color)
{
  self = this; // for inner functions
  this.ok = false;

  if (color.charAt(0) == '#')
    color = color.substr(1,6);
  color = color.replace(/ /,'').toLowerCase();

  var htmlcolors =
  {
    black: '000000',
    silver: 'c0c0c0',
    gray: '808080',
    white: 'ffffff',
    maroon: '800000',
    red: 'ff0000',
    purple: '800080',
    fuchsia: 'ff00ff',
    green: '008000',
    lime: '00ff00',
    olive: '808000',
    yellow: 'ffff00',
    navy: '000080',
    blue: '0000ff',
    teal: '008080',
    aqua: '00ffff'
  };

  for (var name in htmlcolors)
  {
    if (color == name)
    {
      this.name = color;
      color = htmlcolors[name];
    }
  }

  var rgb = /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/.exec(color);
  if (rgb)
  {
    this.red = parseInt(rgb[1], 10);
    this.green = parseInt(rgb[2], 10);
    this.blue = parseInt(rgb[3], 10);
    this.ok = true;
  }
  else
  {
    rgb = /^(\w{2})(\w{2})(\w{2})$/.exec(color);
    if (rgb)
    {
      this.red = parseInt(rgb[1], 16);
      this.green = parseInt(rgb[2], 16);
      this.blue = parseInt(rgb[3], 16);
      this.ok = true;
    }
    else
    {
      rgb = /^(\w{1})(\w{1})(\w{1})$/.exec(color);
      if (rgb)
      {
        this.red = parseInt(rgb[1]+rgb[1], 16);
        this.green = parseInt(rgb[2]+rgb[2], 16);
        this.blue = parseInt(rgb[3]+rgb[3], 16);
        this.ok = true;
      }
    }
  }

  this.red = (this.red < 0 || isNaN(this.red)) ? 0 : ((this.red > 255) ? 255 : this.red);
  this.green = (this.green < 0 || isNaN(this.green)) ? 0 : ((this.green > 255) ? 255 : this.green);
  this.blue = (this.blue < 0 || isNaN(this.blue)) ? 0 : ((this.blue > 255) ? 255 : this.blue);

  this.toRGB = toRGB;
  function toRGB()
  {
    return 'rgb(' + self.red + ', ' + self.green + ', ' + self.blue + ')';
  }

  this.toHex = toHex;
  function toHex()
  {
    var red = self.red.toString(16);
    var green = self.green.toString(16);
    var blue = self.blue.toString(16);
    if (red.length == 1) red = '0' + red;
    if (green.length == 1) green = '0' + green;
    if (blue.length == 1) blue = '0' + blue;
    return '#' + red + green + blue;
  }
}

function Extends(collector, source)
{
  for (var att in source)
    collector[att] = source[att];

  // original source to call parent
  collector.parent = source;
  return collector;
}

// json
function JSONDecode(json)
{
  var code = null;
  try
  {
    // 1. We parse the json code
    code = eval('(' + json + ')');
  }
  catch (e)
  {
    alert('The JSON code has been parsed with error, it cannot be built. \n' + e.message);
    throw e;
  }
  return code;
}

// Unique browser methods

function _browser()
{
  var self = this;

  var agent = navigator.userAgent.toUpperCase();
  this.https = window.location.href.toUpperCase().indexOf("HTTPS") == 0;
  this.opera = agent.indexOf("OPERA") > -1;
  this.msie = !this.opera && agent.indexOf("MSIE") > -1;
  this.msie7sup = (!this.opera && agent.indexOf("MSIE 7") > -1) || (!this.opera && agent.indexOf("MSIE 8") > -1);
  this.msie7 = !this.opera && agent.indexOf("MSIE 7") > -1;
  this.msie8 = !this.opera && agent.indexOf("MSIE 8") > -1;
  this.firefox = !!agent.match(/FIREFOX/);
  this.safari = !!agent.match(/WEBKIT|KHTML/);
  this.safarimobile = !!agent.match(/APPLE.*MOBILE.*SAFARI/);
  this.safari3 = this.safari && agent.indexOf('WEBKIT/5') > -1;
  this.gecko = !this.safari && agent.indexOf('GECKO') > -1;
  this.dom = document.getElementById && document.childNodes && document.createElement;
  this.zindex = 1;
  // normalize button clicks
  this.normalizedMouseButton = this.msie ? {1:0, 2:2, 4:1} : (this.safari && !this.safari3 ? {1:0, 2:1, 3:2} : {0:0, 1:1, 2:2});

  // remove css image flicker
	if (this.msie && !this.msie7sup)
    try { document.execCommand("BackgroundImageCache", false, true); } catch(e) {}

  // ===================================
  // LAYERS FUNCTIONS

  // Will return the next z.index to put things other anything already in window
  this.getNextZIndex = getNextZIndex;
  function getNextZIndex()
  {
    return ++self.zindex;
  }

  // ===================================
  // METRICS FUNCTIONS

  // get the size of the document. The document is the full usable html area
  this.getDocumentWidth = getDocumentWidth;
  function getDocumentWidth()
  {
    if (self.dom && (self.msie7sup || !self.msie))
      return document.documentElement.scrollWidth;
    return document.body.scrollWidth;
  }

  this.getDocumentHeight = getDocumentHeight;
  function getDocumentHeight()
  {
    if (self.dom && (self.msie7sup || !self.msie))
      return document.documentElement.scrollHeight;
    return document.body.scrollHeight;
  }

  // get the size of the window. The window is the browser visible area
  this.getWindowWidth = getWindowWidth;
  function getWindowWidth()
  {
    if (!self.msie)
      return window.innerWidth;

    if (document.documentElement && document.documentElement.clientWidth)
      return document.documentElement.clientWidth;

    if (document.body && document.body.clientWidth)
      return document.body.clientWidth;

    return 0;
  }

  this.getWindowHeight = getWindowHeight;
  function getWindowHeight()
  {
    if (!self.msie)
      return window.innerHeight;

    if( document.documentElement && document.documentElement.clientHeight)
      return document.documentElement.clientHeight;

    if( document.body && document.body.clientHeight)
      return document.body.clientHeight;

    return 0;
  }

  // get the size of the OS/screen
  this.getScreenWidth = getScreenWidth;
  function getScreenWidth()
  {
    return screen.width;
  }

  this.getScreenHeight = getScreenHeight;
  function getScreenHeight()
  {
    return screen.height;
  }

  // get the scroll of the window if the document is bigger than the window
  this.getScrollLeft = getScrollLeft;
  function getScrollLeft()
  {
    if (self.dom) // && (self.msie7 || !self.msie))
      return document.documentElement.scrollLeft;

    // ie6 and before
    if (document.body && document.body.scrollLeft)
      return document.body.scrollLeft;

    // others without dom
    if (typeof window.pageXOffset == 'number')
      return window.pageXOffset;

    return 0;
  }

  this.getScrollTop = getScrollTop;
  function getScrollTop()
  {
    // ie6 and before use BAD the documentelement on dom!
    if (self.dom) // && (self.msie7 || !self.msie))
      return document.documentElement.scrollTop;

    // ie6 and before
    if (document.body && document.body.scrollTop)
      return document.body.scrollTop;

    // others without dom
    if (typeof window.pageYOffset == 'number')
      return window.pageYOffset;

    return 0;
  }

  // get the maximum scroll available
  this.getScrollWidth = getScrollWidth;
  function getScrollWidth()
  {
    return self.getDocumentWidth();
  }

  this.getScrollHeight = getScrollHeight;
  function getScrollHeight()
  {
    return self.getDocumentHeight();
  }

  // get the left of a DOM element into the document
  this.getNodeDocumentLeft = getNodeDocumentLeft;
  function getNodeDocumentLeft(node)
  {
    var l = node.offsetLeft;
    if (node.offsetParent != null)
      l += self.getNodeDocumentLeft(node.offsetParent) + self.getNodeBorderLeftWidth(node.offsetParent);
    return l;
  }

  // get the top of a DOM element into the document
  this.getNodeDocumentTop = getNodeDocumentTop;
  function getNodeDocumentTop(node)
  {
    var t = node.offsetTop;
    if (node.offsetParent != null)
      t += self.getNodeDocumentTop(node.offsetParent) + self.getNodeBorderTopHeight(node.offsetParent);
    return t;
  }

  // get the left of a DOM element into the referenced node. If referenced node is NOT into the fathers, then it will give the left in the document
  this.getNodeNodeLeft = getNodeNodeLeft;
  function getNodeNodeLeft(node, refnode)
  {
    if (!node)
      return null;
    var l = node.offsetLeft;
    if (node.offsetParent != null && node.offsetParent != refnode)
      l += self.getNodeNodeLeft(node.offsetParent, refnode);
    return l;
  }

  // get the top of a DOM element into the referenced node. If referenced node is NOT into the fathers, then it will give the top in the document
  this.getNodeNodeTop = getNodeNodeTop;
  function getNodeNodeTop(node, refnode)
  {
    if (!node)
      return null;
    var t = node.offsetTop;
    if (node.offsetParent != null && node.offsetParent != refnode)
      t += self.getNodeNodeTop(node.offsetParent, refnode);
    return t;
  }

/*
  About size and functions to get sizes:

     | margin | border | padding | content | padding | border | margin |
     |-------- extrawidth -------|- width -|
     |- externalwidth -|-------- innerwidth ---------|
              |----------------- offsetwidth -----------------|
     |--------------------------- outerwidth --------------------------|

  The external is the sum of left and right external
  The extra is the sum of left and right extra

  Same applies with height
*/

  // get the offset size of a DOM element
  this.getNodeOffsetWidth = getNodeOffsetWidth;
  function getNodeOffsetWidth(node)
  {
    return node.offsetWidth;
  }

  this.getNodeOffsetHeight = getNodeOffsetHeight;
  function getNodeOffsetHeight(node)
  {
    return node.offsetHeight;
  }

  // get the real size of a DOM element
  this.getNodeWidth = getNodeWidth;
  function getNodeWidth(node)
  {
    if (self.msie)
    {
      return self.getNodeOffsetWidth(node) - (isNaN(parseInt(node.currentStyle.paddingLeft, 10))?0:parseInt(node.currentStyle.paddingLeft, 10)) - (isNaN(parseInt(node.currentStyle.paddingRight, 10))?0:parseInt(node.currentStyle.paddingRight, 10))
        - (isNaN(parseInt(node.currentStyle.borderLeftWidth, 10))?0:parseInt(node.currentStyle.borderLeftWidth, 10)) - (isNaN(parseInt(node.currentStyle.borderRightWidth, 10))?0:parseInt(node.currentStyle.borderRightWidth, 10));
    }
    var e = window.getComputedStyle(node, null);
    return self.getNodeOffsetWidth(node) - parseInt(e.getPropertyValue('padding-left'), 10) - parseInt(e.getPropertyValue('padding-right'), 10)
                                         - parseInt(e.getPropertyValue('border-left-width'), 10) - parseInt(e.getPropertyValue('border-right-width'), 10);
  }

  this.getNodeHeight = getNodeHeight;
  function getNodeHeight(node)
  {
    if (self.msie)
    {
      return self.getNodeOffsetHeight(node) - (isNaN(parseInt(node.currentStyle.paddingTop, 10))?0:parseInt(node.currentStyle.paddingTop, 10)) - (isNaN(parseInt(node.currentStyle.paddingBottom, 10))?0:parseInt(node.currentStyle.paddingBottom, 10))
        - (isNaN(parseInt(node.currentStyle.borderTopWidth, 10))?0:parseInt(node.currentStyle.borderTopWidth, 10)) - (isNaN(parseInt(node.currentStyle.borderBottomWidth, 10))?0:parseInt(node.currentStyle.borderBottomWidth, 10));
    }
    var e = window.getComputedStyle(node, null);
    return self.getNodeOffsetHeight(node) - parseInt(e.getPropertyValue('padding-top'), 10) - parseInt(e.getPropertyValue('padding-bottom'), 10)
                                          - parseInt(e.getPropertyValue('border-top-width'), 10) - parseInt(e.getPropertyValue('border-bottom-width'), 10);
  }

  this.getNodeInnerWidth = getNodeInnerWidth;
  function getNodeInnerWidth(node)
  {
    if (self.msie)
    {
      return self.getNodeOffsetWidth(node) - (isNaN(parseInt(node.currentStyle.borderLeftWidth, 10))?0:parseInt(node.currentStyle.borderLeftWidth, 10)) - (isNaN(parseInt(node.currentStyle.borderRightWidth, 10))?0:parseInt(node.currentStyle.borderRightWidth, 10));
    }
    var e = window.getComputedStyle(node, null);
    return self.getNodeOffsetWidth(node) - parseInt(e.getPropertyValue('border-left-width'), 10) - parseInt(e.getPropertyValue('border-right-width'), 10);
  }

  this.getNodeInnerHeight = getNodeInnerHeight;
  function getNodeInnerHeight(node)
  {
    if (self.msie)
    {
      return self.getNodeOffsetHeight(node) - (isNaN(parseInt(node.currentStyle.borderTopWidth, 10))?0:parseInt(node.currentStyle.borderTopWidth, 10)) - (isNaN(parseInt(node.currentStyle.borderBottomWidth, 10))?0:parseInt(node.currentStyle.borderBottomWidth, 10));
    }
    var e = window.getComputedStyle(node, null);
    return self.getNodeOffsetHeight(node) - parseInt(e.getPropertyValue('border-top-width'), 10) - parseInt(e.getPropertyValue('border-bottom-width'), 10);
  }

  this.getNodeOuterWidth = getNodeOuterWidth;
  function getNodeOuterWidth(node)
  {
    if (self.msie)
    {
      return self.getNodeOffsetWidth(node) + (isNaN(parseInt(node.currentStyle.marginLeft, 10))?0:parseInt(node.currentStyle.marginLeft, 10)) + (isNaN(parseInt(node.currentStyle.marginRight, 10))?0:parseInt(node.currentStyle.marginRight, 10));
    }
    var e = window.getComputedStyle(node, null);
// $('debug').innerHTML += 'GET NODE OUTER: ' + self.getNodeOffsetWidth(node) + ', '+parseInt(e.getPropertyValue('margin-left'), 10)+', '+parseInt(e.getPropertyValue('margin-right'), 10)+'<br />';

    return self.getNodeOffsetWidth(node) + parseInt(e.getPropertyValue('margin-left'), 10) + parseInt(e.getPropertyValue('margin-right'), 10);
  }

  this.getNodeOuterHeight = getNodeOuterHeight;
  function getNodeOuterHeight(node)
  {
    if (!node)
      return null;
    if (self.msie)
    {
      return self.getNodeOffsetHeight(node) + (isNaN(parseInt(node.currentStyle.marginTop, 10))?0:parseInt(node.currentStyle.marginTop, 10)) + (isNaN(parseInt(node.currentStyle.marginBottom, 10))?0:parseInt(node.currentStyle.marginBottom, 10));
    }
    var e = window.getComputedStyle(node, null);
    return self.getNodeOffsetHeight(node) + parseInt(e.getPropertyValue('margin-top'), 10) + parseInt(e.getPropertyValue('margin-bottom'), 10);
  }

  this.getNodeExternalWidth = getNodeExternalWidth;
  function getNodeExternalWidth(node)
  {
    if (self.msie)
    {
      return (isNaN(parseInt(node.currentStyle.marginLeft, 10))?0:parseInt(node.currentStyle.marginLeft, 10)) + (isNaN(parseInt(node.currentStyle.marginRight, 10))?0:parseInt(node.currentStyle.marginRight, 10))
        + (isNaN(parseInt(node.currentStyle.borderLeftWidth, 10))?0:parseInt(node.currentStyle.borderLeftWidth, 10)) + (isNaN(parseInt(node.currentStyle.borderRightWidth, 10))?0:parseInt(node.currentStyle.borderRightWidth, 10));
    }
    var e = window.getComputedStyle(node, null);
    return parseInt(e.getPropertyValue('margin-left'), 10) + parseInt(e.getPropertyValue('margin-right'), 10)
         + parseInt(e.getPropertyValue('border-left-width'), 10) + parseInt(e.getPropertyValue('border-right-width'), 10);
  }

  this.getNodeExternalHeight = getNodeExternalHeight;
  function getNodeExternalHeight(node)
  {
    if (self.msie)
    {
      return (isNaN(parseInt(node.currentStyle.marginTop, 10))?0:parseInt(node.currentStyle.marginTop, 10)) + (isNaN(parseInt(node.currentStyle.marginBottom, 10))?0:parseInt(node.currentStyle.marginBottom, 10))
        + (isNaN(parseInt(node.currentStyle.borderTopWidth, 10))?0:parseInt(node.currentStyle.borderTopWidth, 10)) + (isNaN(parseInt(node.currentStyle.borderBottomWidth, 10))?0:parseInt(node.currentStyle.borderBottomWidth, 10));
    }
    var e = window.getComputedStyle(node, null);
    return parseInt(e.getPropertyValue('margin-top'), 10) + parseInt(e.getPropertyValue('margin-bottom'), 10)
         + parseInt(e.getPropertyValue('border-top-width'), 10) + parseInt(e.getPropertyValue('border-bottom-width'), 10);
  }

  this.getNodeExtraWidth = getNodeExtraWidth;
  function getNodeExtraWidth(node)
  {
    if (self.msie)
    {
      return self.getNodeExternalWidth(node) + (isNaN(parseInt(node.currentStyle.paddingLeft, 10))?0:parseInt(node.currentStyle.paddingLeft, 10)) + (isNaN(parseInt(node.currentStyle.paddingRight, 10))?0:parseInt(node.currentStyle.paddingRight, 10));
    }
    var e = window.getComputedStyle(node, null);
    return self.getNodeExternalWidth(node) + parseInt(e.getPropertyValue('padding-left'), 10) + parseInt(e.getPropertyValue('padding-right'), 10);
  }

  this.getNodeExtraHeight = getNodeExtraHeight;
  function getNodeExtraHeight(node)
  {
    if (self.msie)
    {
      return self.getNodeExternalHeight(node) + (isNaN(parseInt(node.currentStyle.paddingTop, 10))?0:parseInt(node.currentStyle.paddingTop, 10)) + (parseInt(isNaN(node.currentStyle.paddingBottom, 10))?0:parseInt(node.currentStyle.paddingBottom, 10));
    }
    var e = window.getComputedStyle(node, null);
    return self.getNodeExternalHeight(node) + parseInt(e.getPropertyValue('padding-top'), 10) + parseInt(e.getPropertyValue('padding-bottom'), 10);
  }

  // get left and top borders
  this.getNodeBorderLeftWidth = getNodeBorderLeftWidth;
  function getNodeBorderLeftWidth(node)
  {
    if (self.msie)
    {
      return (isNaN(parseInt(node.currentStyle.borderLeftWidth, 10))?0:parseInt(node.currentStyle.borderLeftWidth, 10));
    }
    var e = window.getComputedStyle(node, null);
    return parseInt(e.getPropertyValue('border-left-width'), 10);
  }

  this.getNodeBorderTopHeight = getNodeBorderTopHeight;
  function getNodeBorderTopHeight(node)
  {
    if (self.msie)
    {
      return (isNaN(parseInt(node.currentStyle.borderTopWidth, 10))?0:parseInt(node.currentStyle.borderTopWidth, 10));
    }
    var e = window.getComputedStyle(node, null);
    return parseInt(e.getPropertyValue('border-top-width'), 10);
  }

  // ===================================
  // MOUSE FUNCTIONS

  /*
    The mouse is not standard on all navigators.
    ie and safari does not map same clicks keys (left, center, right), we need corresponding table

    NOTE Than both mouse and keyboard events are mixed in the same event
  */

  // getCursorNode will return the DOM node in which the event happened
  this.getCursorNode = getCursorNode;
  function getCursorNode(e)
  {
    var ev = e || window.event;
    if (ev.target) return ev.target;
    if (ev.srcElement) return ev.srcElement;
    return null;
  }

  // returns the absolute position of the event in the document
  this.getCursorDocumentX = getCursorDocumentX;
  function getCursorDocumentX(e)
  {
    var ev = e || window.event;
    return ev.clientX + self.getScrollLeft() - (document.documentElement.clientLeft || 0);  // MSIE 7 has a weird 2 pixels offset for mouse coords !
  }

  // returns the absolute position of the event in the document
  this.getCursorDocumentY = getCursorDocumentY;
  function getCursorDocumentY(e)
  {
    var ev = e || window.event;
    return ev.clientY + self.getScrollTop() - (document.documentElement.clientLeft || 0);  // MSIE 7 has a weird 2 pixels offset for mouse coords !
  }

  // returns the absolute position of the event in the browserwindow
  this.getCursorWindowX = getCursorWindowX;
  function getCursorWindowX(e)
  {
    var ev = e || window.event;
    return ev.clientX - (self.msie7sup?2:0);  // MSIE 7 has a weird 2 pixels offset for mouse coords !;
  }

  // returns the absolute position of the event in the browserwindow
  this.getCursorWindowY = getCursorWindowY;
  function getCursorWindowY(e)
  {
    var ev = e || window.event;
    return ev.clientY - (self.msie7sup?2:0);  // MSIE 7 has a weird 2 pixels offset for mouse coords !;
  }

  // returns the absolute position of the event in the container based on the OFFSET metrix (i.e. with border included)
  // IF the function does not work on FIREFOX: DO NOT MODIFY the code,
  //     but add a position: relative to the container !
  // (note: FF and Safari, gets natural origin with border, IE and opera, without border :S)
  this.getCursorOffsetX = getCursorOffsetX;
  function getCursorOffsetX(e)
  {
    var offset = 0;
    if (self.msie || self.opera)
      offset = self.getNodeBorderLeftWidth(self.getCursorNode(e));

    var ev = e || window.event;
    if(typeof(ev.layerX) == 'number')
      return ev.layerX + offset;
    if(typeof(ev.offsetX) == 'number')
      return ev.offsetX + offset;
    return 0;
  }

  // returns the absolute position of the event in the container based on the OFFSET metrix (i.e. with border included)
  // IF the function does not work on FIREFOX: DO NOT MODIFY the code,
  //     but add a position: relative to the container !
  this.getCursorOffsetY = getCursorOffsetY;
  function getCursorOffsetY(e)
  {
    var offset = 0;
    if (self.msie || self.opera)
      offset = self.getNodeBorderTopHeight(self.getCursorNode(e));

    var ev = e || window.event;
    if(typeof(ev.layerY) == 'number')
      return ev.layerY + offset;
    if(typeof(ev.offsetY) == 'number')
      return ev.offsetY + offset;
    return 0;
  }

  // returns the absolute position of the event in the container based on the INNER metrix (i.e. without border included)
  // IF the function does not work on FIREFOX: DO NOT MODIFY the code,
  //     but add a position: relative to the container !
  this.getCursorInnerX = getCursorInnerX;
  function getCursorInnerX(e)
  {
    var offset = 0;
    if (!self.msie && !self.opera)
      offset = self.getNodeBorderLeftWidth(self.getCursorNode(e));

    var ev = e || window.event;
    if(typeof(ev.layerX) == 'number')
      return ev.layerX - offset;
    if(typeof(ev.offsetX) == 'number')
      return ev.offsetX - offset;
    return 0;
  }

  // returns the absolute position of the event in the container based on the INNER metrix (i.e. without border included)
  // IF the function does not work on FIREFOX: DO NOT MODIFY the code,
  //     but add a position: relative to the container !
  this.getCursorInnerY = getCursorInnerY;
  function getCursorInnerY(e)
  {
    var offset = 0;
    if (!self.msie && !self.opera)
      offset = self.getNodeBorderTopHeight(self.getCursorNode(e));

    var ev = e || window.event;
    if(typeof(ev.layerY) == 'number')
      return ev.layerY - offset;
    if(typeof(ev.offsetY) == 'number')
      return ev.offsetY - offset;
    return 0;
  }

  // click functions
  this.getButtonClick = getButtonClick;
  function getButtonClick(e)
  {
    var ev = e || window.event;
    if (ev.type != 'click' && ev.type != 'dblclick')
      return false;
    var button = ev.button ? self.normalizedMouseButton[ev.button] : (ev.which ? ev.which-1 : 0);
    return button;
  }

  // click functions
  this.getButtonPressed = getButtonPressed;
  function getButtonPressed(e)
  {
    var ev = e || window.event;
    if (ev.type != 'mousedown' && ev.type != 'mouseup')
      return false;
    var button = ev.button ? self.normalizedMouseButton[ev.button] : (ev.which ? ev.which-1 : false);
    return button;
  }

  this.getWheel = getWheel;
  function getWheel(e)
  {
    var ev = e || window.event;
    if (ev.type != 'DOMMouseScroll' && ev.type != 'mousewheel')
      return false;
    var delta = 0;
    if(ev.wheelDelta)
    {
      delta = ev.wheelDelta / 120;
    }
    else if (ev.detail)
    {
      delta = -ev.detail / 3;
    }
    return delta;
  }

  this.cancelEvent = cancelEvent;
  function cancelEvent(e)
  {
    var ev = e || window.event;
    if (ev.stopPropagation)
      ev.stopPropagation();
    if (ev.preventDefault)
      ev.preventDefault();
    if (ev.stopEvent)
      ev.stopEvent();
    if (self.msie) window.event.keyCode = 0;
    ev.cancel = true;
    ev.cancelBubble = true;
    ev.returnValue = false;
    return false;
  }

  // ===================================
  // KEYBOARD FUNCTIONS

  /*
    The keyboard is not standard on all navigators.
    known properties: shift, control, alt, keycode, charcode, navigation key
    navigation keys are: arrows, page up/down, insert, home, end, enter, tab escape

    NOTE Than both mouse and keyboard events are mixed in the same event
  */

  // key functions
  this.getKey = getKey;
  function getKey(e)
  {
    var ev = e || window.event;
    if (ev.type != 'keydown' && ev.type != 'keyup')
      return false;
    return ev.keyCode || ev.which;
  }

  this.getChar = getChar;
  function getChar(e)
  {
    var ev = e || window.event;
    if (ev.type != 'keypress')
      return false;
    return String.fromCharCode(ev.charCode ? ev.charCode : ev.keyCode);
  }

  this.ifShift = ifShift;
  function ifShift(e)
  {
    var ev = e || window.event;
    return ev.shiftKey;
  }

  this.ifCtrl = ifCtrl;
  function ifCtrl(e)
  {
    var ev = e || window.event;
    return ev.ctrlKey || ev.metaKey;
  }

  this.ifAlt = ifAlt;
  function ifAlt(e)
  {
    var ev = e || window.event;
    return ev.altKey;
  }

  // any shift, control, alt
  this.ifModifier = ifModifier;
  function ifModifier(e)
  {
    var ev = e || window.event;
    return (ev.altKey || ev.ctrlKey || ev.metaKey || ev.shiftKey) ? true : false;
  }

  // any navigation keys: arrows, page up/down, home/end, escape, enter, tab
  this.ifNavigation = ifNavigation;
  function ifNavigation(e)
  {
    var c = self.getKey(e);
    return ((c >= 33 && c <= 40) || c == 9 || c == 13 || c == 27) ? true : false;
  }

  // f1 to f12
  this.ifFunction = ifFunction;
  function ifFunction(e)
  {
    var c = self.getKey(e);
    return (c >= 112 && c <= 123) ? true : false;
  }

  // ===================================
  // SELECTION FUNCTIONS

  // select something in the document
  this.getSelectionRange = getSelectionRange;
  function getSelectionRange(node, selectionStart, selectionEnd)
  {
    if (node.setSelectionRange)
    {
      node.focus();
      node.setSelectionRange(selectionStart, selectionEnd);
    }
    else if (node.createTextRange)
    {
      var range = node.createTextRange();
      range.collapse(true);
      range.moveEnd('character', selectionEnd);
      range.moveStart('character', selectionStart);
      range.select();
    }
    return;
  }

  // ===================================
  // FILL FUNCTIONS

  // fill an innerHTML
  this.setInnerHTML = setInnerHTML;
  function setInnerHTML(node, content)
  {
    if (self.gecko)
    {
      var rng = document.createRange();
      rng.setStartBefore(node);
      var htmlFrag = rng.createContextualFragment(content);
      while (node.hasChildNodes())
        node.removeChild(node.lastChild);
      node.appendChild(htmlFrag);
    }
    else
    {
      node.innerHTML = content;
    }
    return;
  }

}

// create unique instance of browser
var browser = new _browser();

// WA is a pool of tools for 4GL and WAJAF
var WA = { version: '4.01.01' };

// DEBUG tool
WA.debugConsole = null;    // node value if debug available
WA.debugLevel = 4;         // 1 = system main & user, 2 = main & user, 3 = user only, 4 = nothing
WA.debug = function(message, level)
{
  if (!level) // no level = user level
    level = 3;
  if (WA.debugConsole && level >= WA.debugLevel)
    WA.debugConsole.write(message+'<br />');
  return;
}

var debug = WA.debug;

WA.parseID = function(id, localid)
{
  if (typeof id != 'string')
    return null;
  var xid = null;
  if (id.indexOf('-')==-1)
  {
    if (localid == undefined || !localid || localid == null)
      throw 'Error: the local ID cannot be undefined or null in WA.parseID; id='+id;
    xid = [localid[0], localid[1], id];
  }
  else
  {
    xid = id.split('-');
    if (xid.length != 3)
      return null;
  }
  xid.push(xid[0] + '-' + xid[1] + '-' + xid[2]);
  return xid;
}

// Will create a dom Node of specified type, and apply classname if defined
WA.createDomNode = function(type, id, classname)
{
  var domnode = document.createElement(type);
  if (id)
    domnode.id = id;
  if (classname !== null && classname != undefined)
    domnode.className = classname;
  return domnode;
}

// will search for the index into the array. If value is integer, will just search if exits, either will search into the field of the array
// return index or false if not found
WA.getIndexById = function(array, index, field)
{
  if (typeof index == 'number')
  {
    if (array[index] != undefined)
      return index;
  }
  else
  {
    for (var i = 0, l = array.length; i < l; i++)
      if (array[i][field] == index)
        return i;
  }
  return false;
}

WA.getObjectById = function(array, index, field)
{
  if (typeof index == 'number')
  {
    if (array[index] != undefined)
      return array[index];
  }
  else
  {
    for (var i in array)
    {
      if (typeof array[i] == 'object')
        if (array[i][field] == index)
          return array[i];
    }
  }
  return false;
}

WA.clone = function(obj)
{
  var cloned = {};
  for (var i in obj)
  {
    if (typeof obj[i] == 'object')
      cloned[i] = WA.clone(obj[i]);
    else
      cloned[i] = obj[i];
  }
  return cloned;
}

WA.replaceTemplate = function(obj, rep)
{
  for (var i in obj)
  {
    if (typeof obj[i] == 'string')
    {
      for (var j in rep)
      {
        obj[i] = obj[i].replace(new RegExp('@@'+j+'@@', 'g'), rep[j]);
      }
    }
    else if (typeof obj[i] == 'object')
      WA.replaceTemplate(obj[i], rep);
  }
  return;
}

// render is an object to render data through filters
var render = {};

render.Date = function(format, data)
{
  return 'D:'+data;
}

render.Integer = function(data)
{
  return 'I:'+data;
}

render.Money = function(data)
{
  return '$:'+data;
}

// empty function for listeners assignement (IE bug mainly that does not accept null)
nothing = function() {};

// Original idea of this function by prototype.js @ www.conio.net, improved for webability
var $Context = '';

function $()
{
  if (arguments.length == 1)
  {
    var element = arguments[0];
    if (typeof element == 'string')
      return document.getElementById($Context + element);
  }

  var elements = new Array();

  for (var i = 0, l = arguments.length; i < l; i++)
  {
    var element = arguments[i];

    if (typeof element == 'string')
    {
      el = document.getElementById($context + element);
      elements.push(el);
    }
    else if (typeof element == 'object')
    {
      for (var j = 0, m = element.length; j < m; j++)
      {
        el = document.getElementById($context + element[j]);
        elements.push(el);
      }
    }
  }
  return elements;
}

function min()
{
  var min = +Infinity;
  for (var i = 0, l = arguments.length; i < l; i++)
  {
    if (arguments[i] < min)
      min = arguments[i];
  }
  return min;
}

function max()
{
  var max = -Infinity;
  for (var i = 0, l = arguments.length; i < l; i++)
  {
    if (arguments[i] > max)
      max = arguments[i];
  }
  return max;
}

function functionQueue()
{
  var self = this;
  this.functions = new Array();

  this.registerFunction = registerFunction;
  function registerFunction(fct)
  {
    self.functions[self.functions.length] = fct;
  }

  this.unregisterFunction = unregisterFunction;
  function unregisterFunction(fct)
  {
    for (var i = 0; i < self.functions.length; i++)
    {
    }
  }

  this.destroy = destroy;
  function destroy()
  {
    self.functions = new Array();
  }

  this.call = call;
  function call()
  {
    var args;
    if (arguments.length == 1)
      args = arguments[0];
    else
      args = arguments;

    for (var i = 0; i < self.functions.length; i++)
    {
      self.functions[i](args);
    }
  }

}

var logger =
{
  loggerwindow: null,

  __getWindow: function()
  {
    if (!this.loggerwindow)
    {
      this.loggerwindow = $('logger_window');
    }
  },

  put: function(message)
  {
    this.__getWindow();
    if (this.loggerwindow)
    {
      var text = this.loggerwindow.value;
      this.loggerwindow.value = message + "<br />\r\n" + text;
    }
  },

  clear: function()
  {
    this.__getWindow();
    if (this.loggerwindow)
    {
      this.loggerwindow.value = "";
    }
  }
}

// Date validation function
function isDate(year, month, day)
{
  var numdays = [,31,28,31,30,31,30,31,31,30,31,30,31][month];
  return day>0 && !!numdays && (day<=numdays || day==29 && year%4==0 && (year%100!=0 || year%400==0) )
}

// Hour validation function
function isHour(hour, min, sec)
{
  return hour>=0 && hour<=23 && min>=0 && min<=59 && sec>=0 && sec<=59;
}

// UTF-8 conversions

var utf8 =
{
  // public method for utf8 encoding
  encode : function (value)
  {
    value = value.replace(/\r\n/g,"\n");
    var utf = "";
    for (var i = 0, l = value.length; i < l; i++)
    {
      var c = value.charCodeAt(i);
      if (c < 128)
      {
        utf += String.fromCharCode(c);
      }
      else if((c > 127) && (c < 2048))
      {
        utf += String.fromCharCode((c >> 6) | 192);
        utf += String.fromCharCode((c & 63) | 128);
      }
      else
      {
        utf += String.fromCharCode((c >> 12) | 224);
        utf += String.fromCharCode(((c >> 6) & 63) | 128);
        utf += String.fromCharCode((c & 63) | 128);
      }
    }
    return utf;
  },

  // public method for utf8 decoding
  decode : function (value)
  {
    var str = "";
    var i = 0;
    var c1 = c2 = c3 = 0;
    while ( i < value.length )
    {
      c1 = value.charCodeAt(i);
      if (c1 < 128)
      {
        str += String.fromCharCode(c1);
        i++;
      }
      else if((c1 > 191) && (c1 < 224))
      {
        c2 = value.charCodeAt(i+1);
        str += String.fromCharCode(((c1 & 31) << 6) | (c2 & 63));
        i += 2;
      }
      else
      {
        c2 = value.charCodeAt(i+1);
        c3 = value.charCodeAt(i+2);
        str += String.fromCharCode(((c1 & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
        i += 3;
      }
    }
    return str;
  }
}

// color transfer
function RGBColor(color)
{
  this.ok = false;

  if (color.charAt(0) == '#')
    color = color.substr(1,6);
  color = color.replace(/ /,'').toLowerCase();

  var htmlcolors =
  {
    black: '000000',
    silver: 'c0c0c0',
    gray: '808080',
    white: 'ffffff',
    maroon: '800000',
    red: 'ff0000',
    purple: '800080',
    fuchsia: 'ff00ff',
    green: '008000',
    lime: '00ff00',
    olive: '808000',
    yellow: 'ffff00',
    navy: '000080',
    blue: '0000ff',
    teal: '008080',
    aqua: '00ffff'
  };

  for (var name in htmlcolors)
  {
    if (color == name)
    {
      this.name = color;
      color = htmlcolors[name];
    }
  }

  var rgb = /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/.exec(color);
  if (rgb)
  {
    this.red = parseInt(rgb[1], 10);
    this.green = parseInt(rgb[2], 10);
    this.blue = parseInt(rgb[3], 10);
    this.ok = true;
  }
  else
  {
    rgb = /^(\w{2})(\w{2})(\w{2})$/.exec(color);
    if (rgb)
    {
      this.red = parseInt(rgb[1], 16);
      this.green = parseInt(rgb[2], 16);
      this.blue = parseInt(rgb[3], 16);
      this.ok = true;
    }
    else
    {
      rgb = /^(\w{1})(\w{1})(\w{1})$/.exec(color);
      if (rgb)
      {
        this.red = parseInt(rgb[1]+rgb[1], 16);
        this.green = parseInt(rgb[2]+rgb[2], 16);
        this.blue = parseInt(rgb[3]+rgb[3], 16);
        this.ok = true;
      }
    }
  }

  this.red = (this.red < 0 || isNaN(this.red)) ? 0 : ((this.red > 255) ? 255 : this.red);
  this.green = (this.green < 0 || isNaN(this.green)) ? 0 : ((this.green > 255) ? 255 : this.green);
  this.blue = (this.blue < 0 || isNaN(this.blue)) ? 0 : ((this.blue > 255) ? 255 : this.blue);

  this.toRGB = function()
  {
    return 'rgb(' + this.red + ', ' + this.green + ', ' + this.blue + ')';
  }

  this.toHex = function()
  {
    var red = this.red.toString(16);
    var green = this.green.toString(16);
    var blue = this.blue.toString(16);
    if (red.length == 1) red = '0' + red;
    if (green.length == 1) green = '0' + green;
    if (blue.length == 1) blue = '0' + blue;
    return '#' + red + green + blue;
  }
}


// Unique browser methods

var browser =
{
  https: false,
  msie: false,
  ns: false,
  safari: false,
  dom: false,
  version: null,
  zindex: 1,

  main: function()
  {
    if (document.location.href.indexOf("https")==0)
    {
      this.https=true;
    }
    if (document.getElementById && document.childNodes && document.createElement )
    {
      this.dom=true;
    }
    if ((pos=navigator.userAgent.indexOf('MSIE'))>=0)
    {
      this.msie=true;
      this.version=parseFloat(navigator.userAgent.substr(pos+4));
      return true;
    }
    if ((pos=navigator.userAgent.indexOf('Netscape6/'))>=0)
    {
      this.ns=true;
      this.version=parseFloat(navigator.userAgent.substr(pos+10));
      return true;
    }
    if ((pos=navigator.userAgent.indexOf('Safari/'))>=0)
    {
      this.safari=true;
      this.version=parseFloat(navigator.userAgent.substr(pos+7));
      return true;
    }
    if (navigator.userAgent.indexOf('Gecko')>=0)
    {
      this.ns=true;
      this.version=6.1;
      return true;
    }
    return false; // not recognized browser
  },

  isHttps: function()
  {
    return this.https;
  },

  isIE: function()
  {
    return this.msie;
  },

  isNS: function()
  {
    return this.ns;
  },

  getVersion: function()
  {
    return this.version;
  },

  // POSITIONING FUNCTIONS
  // Will return the next z.index to put things other anything already in window
  getNextZIndex: function()
  {
    return ++this.zindex;
  },

  // SIZE FUNCTIONS
  // get the size of the document
  getDocumentWidth: function()
  {
    if(this.dom && ((this.msie && this.version >= 7) || !this.msie))
      return document.documentElement.scrollWidth;
    return document.body.scrollWidth;
  },

  getDocumentHeight: function()
  {
    if(this.dom && ((this.msie && this.version >= 7) || !this.msie))
      return document.documentElement.scrollHeight;
    return document.body.scrollHeight;
  },

  getWindowWidth: function()
  {
    if (!this.msie)
      return window.innerWidth;

    if( document.documentElement && document.documentElement.clientWidth)
      return document.documentElement.clientWidth;

    if( document.body && document.body.clientWidth)
      return document.body.clientWidth;

    return 0;
  },

  getWindowHeight: function()
  {
    if (!this.msie)
      return window.innerHeight;

    if( document.documentElement && document.documentElement.clientHeight)
      return document.documentElement.clientHeight;

    if( document.body && document.body.clientHeight)
      return document.body.clientHeight;

    return 0;
  },

  // get the size of the OS/screen
  getScreenWidth: function()
  {
    return screen.width;
  },

  // get the size of the OS/screen
  getScreenHeight: function()
  {
    return screen.height;
  },

  // get the scroll of the window if bigger than the browser
  getScrollLeft: function()
  {
    // ie6 and before use BAD the documentelement on dom!
    if(this.dom && ((this.msie && this.version >= 7) || !this.msie))
      return document.documentElement.scrollLeft;

    // ie6 and before
    if(document.body && document.body.scrollLeft)
      return document.body.scrollLeft;

    // others without dom
    if(typeof(window.pageXOffset) == 'number')
      return window.pageXOffset;

    return 0;
  },

  // get the scroll of the window if bigger than the browser
  getScrollTop: function()
  {
    // ie6 and before use BAD the documentelement on dom!
    if(this.dom && ((this.msie && this.version >= 7) || !this.msie))
      return document.documentElement.scrollTop;

    // ie6 and before
    if(document.body && document.body.scrollTop)
      return document.body.scrollTop;

    // others without dom
    if(typeof(window.pageYOffset) == 'number')
      return window.pageYOffset;

    return 0;
  },

  // ==== Older compatibility functions
  getClientWidth: function()
  {
    return this.getWindowWidth();
  },

  getClientHeight: function()
  {
    return this.getWindowHeight();
  },

  // get the maximum scroll available
  getScrollWidth: function()
  {
    return this.getDocumentWidth();
  },

  // get the maximum scroll available
  getScrollHeight: function()
  {
    return this.getDocumentHeight();
  },

  // get the left of a DOM element into the document
  getNodeDocumentLeft: function(node)
  {
    var l = node.offsetLeft;
    if (node.offsetParent != null)
      l += this.getNodeDocumentLeft(node.offsetParent);
    return l;
  },

  // get the top of a DOM element into the document
  getNodeDocumentTop: function(node)
  {
    var t = node.offsetTop;
    if (node.offsetParent != null)
      t += this.getNodeDocumentTop(node.offsetParent);
    return t;
  },

  // get the left of a DOM element into the referenced node. If referenced node is NOT into the fathers, then it will give the left in the document
  getNodeNodeLeft: function(node, refnode)
  {
    var l = node.offsetLeft;
    if (node.offsetParent != null && node.offsetParent != refnode)
      l += this.getNodeNodeLeft(node.offsetParent, refnode);
    return l;
  },

  // get the top of a DOM element into the referenced node. If referenced node is NOT into the fathers, then it will give the top in the document
  getNodeNodeTop: function(node, refnode)
  {
    var t = node.offsetTop;
    if (node.offsetParent != null && node.offsetParent != refnode)
      t += this.getNodeNodeTop(node.offsetParent, refnode);
    return t;
  },

/*
  About size and functions to get sizes:

     | margin | border | padding | content | padding | border | margin |
     |-------- extrawidth -------|- width -|
     |- externalwidth -|-------- innerwidth ---------|
              |----------------- offsetwidth -----------------|
     |--------------------------- outerwidth --------------------------|

  The external is the sum of left and right external
  The extra is the sum of left and right extra

  Same applies with height
*/

  // get the offset size of a DOM element
  getNodeOffsetWidth: function(node)
  {
    return node.offsetWidth;
  },

  getNodeOffsetHeight: function(node)
  {
    return node.offsetHeight;
  },

  // get the real size of a DOM element
  getNodeWidth: function(node)
  {
    if (this.msie)
    {
      return this.getNodeOffsetWidth(node) - (isNaN(parseInt(node.currentStyle.paddingLeft, 10))?0:parseInt(node.currentStyle.paddingLeft, 10)) - (isNaN(parseInt(node.currentStyle.paddingRight, 10))?0:parseInt(node.currentStyle.paddingRight, 10))
        - (isNaN(parseInt(node.currentStyle.borderLeftWidth, 10))?0:parseInt(node.currentStyle.borderLeftWidth, 10)) - (isNaN(parseInt(node.currentStyle.borderRightWidth, 10))?0:parseInt(node.currentStyle.borderRightWidth, 10));
    }
    else
    {
      var e = window.getComputedStyle(node,null);
      return this.getNodeOffsetWidth(node) - parseInt(e.getPropertyValue('padding-left'), 10) - parseInt(e.getPropertyValue('padding-right'), 10)
                                           - parseInt(e.getPropertyValue('border-left-width'), 10) - parseInt(e.getPropertyValue('border-right-width'), 10);
    }
    return this.getNodeOffsetWidth(node) - (node.style.paddingLeft?parseInt(node.style.paddingLeft, 10):0) - (node.style.paddingRight?parseInt(node.style.paddingRight, 10):0)
        - (node.style.borderLeftWidth?parseInt(node.style.borderLeftWidth, 10):0) - (node.style.borderRightWidth?parseInt(node.style.borderRightWidth, 10):0);
  },

  getNodeHeight: function(node)
  {
    if (this.msie)
    {
      return this.getNodeOffsetHeight(node) - (isNaN(parseInt(node.currentStyle.paddingTop, 10))?0:parseInt(node.currentStyle.paddingTop, 10)) - (isNaN(parseInt(node.currentStyle.paddingBottom, 10))?0:parseInt(node.currentStyle.paddingBottom, 10))
        - (isNaN(parseInt(node.currentStyle.borderTopWidth, 10))?0:parseInt(node.currentStyle.borderTopWidth, 10)) - (isNaN(parseInt(node.currentStyle.borderBottomWidth, 10))?0:parseInt(node.currentStyle.borderBottomWidth, 10));
    }
    else
    {
      var e = window.getComputedStyle(node,null);
      return this.getNodeOffsetHeight(node) - parseInt(e.getPropertyValue('padding-top'), 10) - parseInt(e.getPropertyValue('padding-bottom'), 10)
                                           - parseInt(e.getPropertyValue('border-top-width'), 10) - parseInt(e.getPropertyValue('border-bottom-width'), 10);
    }
    return this.getNodeOffsetHeight(node) - (node.style.paddingTop?parseInt(node.style.paddingTop, 10):0) - (node.style.paddingBottom?parseInt(node.style.paddingBottom, 10):0)
        - (node.style.borderTopWidth?parseInt(node.style.borderTopWidth, 10):0) - (node.style.borderBottomWidth?parseInt(node.style.borderBottomWidth, 10):0);
  },

  // get the real internal size of a node, we recommend to use THIS FUNCTION instead of getNodeHeight and getNodeWidth to put things INSIDE
  getNodeInnerWidth: function(node)
  {
    if (this.msie)
    {
      return this.getNodeOffsetWidth(node) - (isNaN(parseInt(node.currentStyle.borderLeftWidth, 10))?0:parseInt(node.currentStyle.borderLeftWidth, 10)) - (isNaN(parseInt(node.currentStyle.borderRightWidth, 10))?0:parseInt(node.currentStyle.borderRightWidth, 10));
    }
    else
    {
      var e = window.getComputedStyle(node,null);
      return this.getNodeOffsetWidth(node) - parseInt(e.getPropertyValue('border-left-width'), 10) - parseInt(e.getPropertyValue('border-right-width'), 10);
    }
    return this.getNodeOffsetWidth(node) - (node.style.borderLeftWidth?parseInt(node.style.borderLeftWidth, 10):0) - (node.style.borderRightWidth?parseInt(node.style.borderRightWidth, 10):0);
  },

  getNodeInnerHeight: function(node)
  {
    if (this.msie)
    {
      return this.getNodeOffsetHeight(node) - (isNaN(parseInt(node.currentStyle.borderTopWidth, 10))?0:parseInt(node.currentStyle.borderTopWidth, 10)) - (isNaN(parseInt(node.currentStyle.borderBottomWidth, 10))?0:parseInt(node.currentStyle.borderBottomWidth, 10));
    }
    else
    {
      var e = window.getComputedStyle(node,null);
      return this.getNodeOffsetHeight(node) - parseInt(e.getPropertyValue('border-top-width'), 10) - parseInt(e.getPropertyValue('border-bottom-width'), 10);
    }
    return this.getNodeOffsetHeight(node) - (node.style.borderTopWidth?parseInt(node.style.borderTopWidth, 10):0) - (node.style.borderBottomWidth?parseInt(node.style.borderBottomWidth, 10):0);
  },

  // get the real outer size of a DOM element (border + margin left and right, up and down)
  // we recommend to use THIS FUNCTION instead of getNodeHeight and getNodeWidth to get real size for collocation into containers
  getNodeOuterWidth: function(node)
  {
    if (this.msie)
    {
      return this.getNodeOffsetWidth(node) + (isNaN(parseInt(node.currentStyle.marginLeft, 10))?0:parseInt(node.currentStyle.marginLeft, 10)) + (isNaN(parseInt(node.currentStyle.marginRight, 10))?0:parseInt(node.currentStyle.marginRight, 10));
    }
    else
    {
      var e = window.getComputedStyle(node,null);
      return this.getNodeOffsetWidth(node) + parseInt(e.getPropertyValue('margin-left'), 10) + parseInt(e.getPropertyValue('margin-right'), 10);
    }
    return this.getNodeOffsetWidth(node) + (node.style.marginLeft?parseInt(node.style.marginLeft, 10):0) + (node.style.marginRight?parseInt(node.style.marginRight, 10):0);
  },

  getNodeOuterHeight: function(node)
  {
    if (this.msie)
    {
      return this.getNodeOffsetHeight(node) + (isNaN(parseInt(node.currentStyle.marginTop, 10))?0:parseInt(node.currentStyle.marginTop, 10)) + (isNaN(parseInt(node.currentStyle.marginBottom, 10))?0:parseInt(node.currentStyle.marginBottom, 10));
    }
    else
    {
      var e = window.getComputedStyle(node,null);
      return this.getNodeOffsetHeight(node) + parseInt(e.getPropertyValue('margin-top'), 10) + parseInt(e.getPropertyValue('margin-bottom'), 10);
    }
    return this.getNodeOffsetHeight(node) + (node.style.marginTop?parseInt(node.style.marginTop, 10):0) + (node.style.marginBottom?parseInt(node.style.marginBottom, 10):0);
  },

  // get the real external size of a DOM element (border + margin left and right, up and down)
  // we recommend to use THIS FUNCTION instead of getNodeHeight and getNodeWidth to get real size for collocation into containers
  getNodeExternalWidth: function(node)
  {
    if (this.msie)
    {
      return (isNaN(parseInt(node.currentStyle.marginLeft, 10))?0:parseInt(node.currentStyle.marginLeft, 10)) + (isNaN(parseInt(node.currentStyle.marginRight, 10))?0:parseInt(node.currentStyle.marginRight, 10))
        + (isNaN(parseInt(node.currentStyle.borderLeftWidth, 10))?0:parseInt(node.currentStyle.borderLeftWidth, 10)) + (isNaN(parseInt(node.currentStyle.borderRightWidth, 10))?0:parseInt(node.currentStyle.borderRightWidth, 10));
    }
    else
    {
      var e = window.getComputedStyle(node,null);
      return parseInt(e.getPropertyValue('margin-left'), 10) + parseInt(e.getPropertyValue('margin-right'), 10)
           + parseInt(e.getPropertyValue('border-left-width'), 10) + parseInt(e.getPropertyValue('border-right-width'), 10);
    }
    return (node.style.marginLeft?parseInt(node.style.marginLeft, 10):0) + (node.style.marginRight?parseInt(node.style.marginRight, 10):0)
        + (node.style.borderLeftWidth?parseInt(node.style.borderLeftWidth, 10):0) + (node.style.borderRightWidth?parseInt(node.style.borderRightWidth, 10):0);
  },

  getNodeExternalHeight: function(node)
  {
    if (this.msie)
    {
      return (isNaN(parseInt(node.currentStyle.marginTop, 10))?0:parseInt(node.currentStyle.marginTop, 10)) + (isNaN(parseInt(node.currentStyle.marginBottom, 10))?0:parseInt(node.currentStyle.marginBottom, 10))
        + (isNaN(parseInt(node.currentStyle.borderTopWidth, 10))?0:parseInt(node.currentStyle.borderTopWidth, 10)) + (isNaN(parseInt(node.currentStyle.borderBottomWidth, 10))?0:parseInt(node.currentStyle.borderBottomWidth, 10));
    }
    else
    {
      var e = window.getComputedStyle(node,null);
      return parseInt(e.getPropertyValue('margin-top'), 10) + parseInt(e.getPropertyValue('margin-bottom'), 10)
           + parseInt(e.getPropertyValue('border-top-width'), 10) + parseInt(e.getPropertyValue('border-bottom-width'), 10);
    }
    return (node.style.marginTop?parseInt(node.style.marginTop, 10):0) + (node.style.marginBottom?parseInt(node.style.marginBottom, 10):0)
        + (node.style.borderTopWidth?parseInt(node.style.borderTopWidth, 10):0) + (node.style.borderBottomWidth?parseInt(node.style.borderBottomWidth, 10):0);
  },

  // get the extra size of a DOM element (border + margin + padding left and right, up and down)
  getNodeExtraWidth: function(node)
  {
    if (this.msie)
    {
      return this.getNodeExternalWidth(node) + (isNaN(parseInt(node.currentStyle.paddingLeft, 10))?0:parseInt(node.currentStyle.paddingLeft, 10)) + (isNaN(parseInt(node.currentStyle.paddingRight, 10))?0:parseInt(node.currentStyle.paddingRight, 10));
    }
    else
    {
      var e = window.getComputedStyle(node,null);
      return this.getNodeExternalWidth(node) + parseInt(e.getPropertyValue('padding-left'), 10) + parseInt(e.getPropertyValue('padding-right'), 10);
    }
    return this.getNodeExternalWidth(node) + (node.style.paddingLeft?parseInt(node.style.paddingLeft, 10):0) + (node.style.paddingRight?parseInt(node.style.paddingRight, 10):0);
  },

  getNodeExtraHeight: function(node)
  {
    if (this.msie)
    {
      return this.getNodeExternalHeight(node) + (isNaN(parseInt(node.currentStyle.paddingTop, 10))?0:parseInt(node.currentStyle.paddingTop, 10)) + (parseInt(isNaN(node.currentStyle.paddingBottom, 10))?0:parseInt(node.currentStyle.paddingBottom, 10));
    }
    else
    {
      var e = window.getComputedStyle(node,null);
      return this.getNodeExternalHeight(node) + parseInt(e.getPropertyValue('padding-top'), 10) + parseInt(e.getPropertyValue('padding-bottom'), 10);
    }
    return this.getNodeExternalHeight(node) + (node.style.paddingTop?parseInt(node.style.paddingTop, 10):0) + (node.style.paddingBottom?parseInt(node.style.paddingBottom, 10):0);
  },


  // MOUSE FUNCTIONS
  // getCursorNode will return the DOM node in which the event happened
  getCursorNode: function(e)
  {
    if (!e) var e = window.event;
    if (e.target) return e.target;
    if (e.srcElement) return e.srcElement;
    return null;
  },

  // returns the absolute position of the event in the document
  getCursorX: function(e)
  {
    if (!e) var e = window.event;
    return e.clientX+this.getScrollLeft();
  },

  // returns the absolute position of the event in the document
  getCursorY: function(e)
  {
    if (!e) var e = window.event;
    return e.clientY+this.getScrollTop();
  },

  // returns the absolute position of the event in the browserwindow
  getCursorWindowX: function(e)
  {
    if (!e) var e = window.event;
    return e.clientX;
  },

  // returns the absolute position of the event in the browserwindow
  getCursorWindowY: function(e)
  {
    if (!e) var e = window.event;
    return e.clientY;
  },

  // returns the absolute position of the event in the container
  // IF the function does not work on FIREFOX: DO NOT MODIFY the code,
  //     but add a position: relative to the container !
  // FF starts the offset at 1, not at 0 !
  getCursorOffsetX: function(e)
  {
    if (!e) var e = window.event;
    if(typeof(e.layerX) == 'number')
      return e.layerX - (browser.ns?1:0);
    if(typeof(e.offsetX) == 'number')
      return e.offsetX - (browser.ns?1:0);
  },

  // returns the absolute position of the event in the container
  // IF the function does not work on FIREFOX: DO NOT MODIFY the code,
  //     but add a position: relative to the container !
  // FF starts the offset at 1, not at 0 !
  getCursorOffsetY: function(e)
  {
    if (!e) var e = window.event;
    if(typeof(e.layerY) == 'number')
      return e.layerY - (browser.ns?1:0);
    if(typeof(e.offsetY) == 'number')
      return e.offsetY - (browser.ns?1:0);
  },

  // OBSOLETE FUNCTIONS for compatibility
  getOffsetX: function(e)
  {
    return this.getCursorOffsetX(e);
  },

  // returns the offset of the event in the container
  getOffsetY: function(e)
  {
    return this.getCursorOffsetY(e);
  },

  // key functions
  getKey: function(e)
  {
    if (!e) var e = window.event;
    var key;
    if (this.msie)
      key = e.keyCode;
    else
      key = e.which;
    return key;
  },

  ifShift: function(e)
  {
    if (!e) var e = window.event;
    return e.shiftKey;
  },

  ifCtrl: function(e)
  {
    if (!e) var e = window.event;
    return e.ctrlKey;
  },

  ifAlt: function(e)
  {
    if (!e) var e = window.event;
    return e.altKey;
  },

  // click functions
  ifRightClick: function(e)
  {
    if (!e) var e = window.event;
  },

  ifCenterClick: function(e)
  {
    if (!e) var e = window.event;
  },

  ifLeftClick: function(e)
  {
    if (!e) var e = window.event;
  },

  ifDoubleRightClick: function(e)
  {
    if (!e) var e = window.event;
  },

  ifDoubleCenterClick: function(e)
  {
    if (!e) var e = window.event;
  },

  ifDoubleLeftClick: function(e)
  {
    if (!e) var e = window.event;
  },

  // SELECTION FUNCTIONS
  // select something in the document
  setSelectionRange: function(obj, selectionStart, selectionEnd)
  {
    if (obj.setSelectionRange)
    {
      obj.focus();
      obj.setSelectionRange(selectionStart, selectionEnd);
    }
    else if (obj.createTextRange)
    {
      var range = obj.createTextRange();
      range.collapse(true);
      range.moveEnd('character', selectionEnd);
      range.moveStart('character', selectionStart);
      range.select();
    }
  },

  // FILL FUNCTIONS
  // fill an innerHTML
  setInnerHTML: function(node, content)
  {
    if (this.ns) //document.getElementById && !document.all
    {
      rng = document.createRange();
      rng.setStartBefore(node);
      htmlFrag = rng.createContextualFragment(content);
      while (node.hasChildNodes())
        node.removeChild(node.lastChild);
      node.appendChild(htmlFrag);
    }
    else
    {
      // please DESTROY content before (IE adores to accumulate and use MEMORY !!!!)
      while (node.hasChildNodes())
      {
        // please AVOID DESTROY TOO MUCH if the nodes are too deep (>1000 nodes = IE blocks for minutes !!!!)
//        this.destroyall(node.lastChild);
        node.removeChild(node.lastChild);
      }
      node.innerHTML = content;
    }
  },

  destroyall: function(node)
  {
    while (node.hasChildNodes())
    {
      this.destroyall(node.lastChild);
      node.removeChild(node.lastChild);
    }
    // unlink anything of this node
    for (i in node)
    {
      if (typeof node[i] === 'function')
      {
        node[i] = null;
      }
    }
  }

}

// We init browser object
browser.main();