
/* core.js, WAJAF, the WebAbility(r) Javascript Application Framework
   Contains multi purpose functions, browser and WA objects
   (c) 2008 Philippe Thomassigny
*/

// 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.debugfilter = null;     // array of filtered words, null = all shown
WA.debug = function(message, level)
{
  if (!level) // no level = user level
    level = 3;
  if (WA.debugConsole && level >= WA.debugLevel)
  {
    if (typeof WA.debugfilter == 'array')
    {
      for (var i in WA.debugfilter)
      {

      }
    }
    WA.debugConsole.write(message+'<br />');
  }
}

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 exists, 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;
  }
  for (var i = 0, l = array.length; i < l; i++)
    if (array[i][field] == index)
      return i;
  return false;
}

// return object or undefined if not found
WA.getObjectById = function(array, index, field)
{
  if (typeof index == 'number')
  {
    if (array[index] != undefined)
      return array[index];
  }
  for (var i in array)
  {
    if (typeof array[i] == 'object')
      if (array[i][field] == index)
        return array[i];
  }
  return undefined;
}

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);
  }
}

WA.getClasses = function(params, defaults)
{
  var c = {};
  for (var i in defaults)
  {
    if (params[i] != undefined)
      c[i] = params[i];
    else
      c[i] = defaults[i];
  }
  return c;
}

// The context is used ONLY if there is no '-' into the element name.
// The context is used for all $, $N, $C, $E, $A
WA.context = '';

WA.setContext = function(context)
{
  var oldcontext = WA.context;
  WA.context = context;
  return oldcontext;
}

// fast alias
var setContext = WA.setContext;

WA.getContext = function()
{
  return WA.context;
}

// fast alias
var getContext = WA.getContext;

WA.getDomNode = function(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(WA.context + domID) || document.getElementById(domID);
    return document.getElementById(domID);
  }
  return null;
}

/* ALIAS to be compatible with Prototype @ www.conio.net */
var $ = WA.getDomNode;

WA.get_4glNode = function(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(WA.context + ID) || wa4glManager.getNode(ID);
    return wa4glManager.getNode(ID);
  }
  return null;
}

/* ALIAS for fast use */
var $N = WA.get_4glNode;

WA.getiApplication = function(ID)
{
  if (arguments.length > 1)
  {
    var elements = [];
    for (var i = 0, l = arguments.length; i < l; i++)
      elements.push(WA.getiApplication(arguments[i]));
    return elements;
  }
  var node = $N(ID);
  if (node)
    return node.iapplication;
  return null;
}

/* ALIAS for fast use */
var $A = WA.getiApplication;

WA.getiContainer = function(ID)
{
  if (arguments.length > 1)
  {
    var elements = [];
    for (var i = 0, l = arguments.length; i < l; i++)
      elements.push(WA.getiContainer(arguments[i]));
    return elements;
  }
  var node = $N(ID);
  if (node)
    return node.icontainer;
  return null;
}

/* ALIAS for fast use */
var $C = WA.getiContainer;

WA.getiElement = function(ID)
{
  if (arguments.length > 1)
  {
    var elements = [];
    for (var i = 0, l = arguments.length; i < l; i++)
      elements.push(WA.getiElement(arguments[i]));
    return elements;
  }
  var node = $N(ID);
  if (node)
    return node.ielement;
  return null;
}

/* ALIAS for fast use */
var $E = WA.getiElement;



// GENERIC TOOLS

// 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;
}

function JSONEncode(data)
{
  var json = '';
  if (typeof data == 'array')
  {
    json += '[';
    var item = 0;
    for (var i in data)
    {
      json += (item++?',':'') + JSONEncode(data[i]);
    }
    json += ']';
  }
  else if (typeof data == 'object')
  {
    json += '{';
    var item=0;
    for (var i in data)
    {
      json += (item++?',':'')+'"'+i+'":'+JSONEncode(data[i]);
    }
    json += '}';
  }
  else if (typeof data == 'number')
  {
    json += data;
  }
  else if (typeof data == 'string')
  {
    json += '"' + data.replace('\\', '\\\\', 'gm').replace('"', '\\"', 'gm') + '"';
  }
  else if (typeof data == 'boolean')
  {
    json += data?'true':'false';
  }
  else if (typeof data == 'function') // we Ignore functions, IE bug
  {
  }
  else
  {
    alert('tipo no conocido: '+typeof data);
  }
  return json;
}

// 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.msie6 = this.msie && !this.msie7sup;
  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;
  }

  // get the scroll of the node if the content is bigger than the node
  this.getNodeScrollLeft = getNodeScrollLeft;
  function getNodeScrollLeft(node)
  {
    if (self.dom) // && (self.msie7 || !self.msie))
      return node.scrollLeft;

    // others without dom
    if (typeof node.pageXOffset == 'number')
      return node.pageXOffset;

    return 0;
  }

  this.getNodeScrollTop = getNodeScrollTop;
  function getNodeScrollTop(node)
  {
    if (self.dom) // && (self.msie7 || !self.msie))
      return node.scrollTop;

    // others without dom
    if (typeof node.pageYOffset == 'number')
      return node.pageYOffset;

    return 0;
  }

  // get the maximum scroll available
  this.getNodeScrollWidth = getNodeScrollWidth;
  function getNodeScrollWidth()
  {
    return self.getDocumentWidth();
  }

  this.getNodeScrollHeight = getNodeScrollHeight;
  function getNodeScrollHeight()
  {
    return self.getDocumentHeight();
  }

/*
  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();
    }
  }

  // ===================================
  // 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;
    }
  }

}

// create unique instance of browser
var browser = new _browser();

// render is an object to render data through filters
var render = {};

render.Date = function(format, data)
{
//  return data.substr(0, 4)+ data.substr(5, 2)+ data.substr(8, 2)
//  var d = new Date(data.substr(0, 4), data.substr(5, 2), data.substr(8, 2));

  var f = data.substr(8, 2) + '/' + data.substr(5, 2) + '/' + data.substr(0, 4);
  return f;
}

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() {};

/*
    eventManager.js, WAJAF, the WebAbility(r) Javascript Application Framework
    Contains the Manager singleton to manage browser Events
    (c) 2008-2010 Philippe Thomassigny

    This file is part of WAJAF

    WAJAF is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    WAJAF is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with WAJAF.  If not, see <http://www.gnu.org/licenses/>.
*/

function _eventManager()
{
  // All the attributes are PRIVATE.
  var self = this;
  listenerid = 1;
  functionid = 1;
  events = {};
  beforeflushs = [];
  flushs = [];
  keys = [];
  specialkeys =
  {
    'esc': 27, 'escape': 27,
    'tab': 9, 'space': 32,
    'return': 13, 'enter': 13,
    'scrolllock': 145, 'capslock': 20, 'numlock': 144,
    'pause': 19, 'break': 19,
    'insert': 45, 'delete': 46, 'backspace': 8,
    'home': 36, 'end': 35, 'pageup': 33, 'pagedown': 34,
    'left': 37, 'up': 38, 'right': 39, 'down': 40,
    'f1': 112, 'f2': 113, 'f3': 114, 'f4': 115, 'f5': 116, 'f6': 117, 'f7': 118, 'f8': 119, 'f9': 120, 'f10': 121, 'f11': 122, 'f12': 123
  };

  // eventname: one of: mousedown, mouseup, click, dblclick, mousemove,
  // mouseover, mouseout, mousewheel,
  // keydown, keyup, keypress, load, unload, scroll,
  // focus, blur, change, submit, abort, error, reset, resize
  // eventnode: the id or the node itself
  // eventfunction: pointer to the function to execute when the event happens
  // eventcapture: true/false to notify if this event is greedy
  this.addListener = this.on = this.add = this.start = this.listen = this.attachEvent = this.registerEvent = addListener;
  function addListener(eventname, eventnode, eventfunction, eventcapture)
  {
    if (typeof eventnode == 'string')
      eventnode = $(eventnode);
    if (!eventnode) // no node found ?
      return false;
    //debug('eventManager.addListener('+eventname+', '+eventnode.id+')');

    // link the UID to the node
    if (eventnode.listeneruid == undefined)
      eventnode.listeneruid = listenerid++;
    if (eventfunction.functionuid == undefined)
      eventfunction.functionuid = functionid++;

    if (events[eventnode.listeneruid] == undefined)
      events[eventnode.listeneruid] = {};
    if (events[eventnode.listeneruid][eventname] == undefined)
      events[eventnode.listeneruid][eventname] = {};

    // get the context from the ID of the node if any
    eventnode.context = WA.context;

    thefunction = function()
    {
      if (WA.context != undefined)
      {
        var xid = oldcontext = null;
        if (this.id && this.id.indexOf('|') != -1)
        {
          xid = WA.parseID(this.id);
          oldcontext = WA.context = xid[0] + '|' + xid[1] + '|';
        }
      }
      var ret = eventfunction.apply(this, arguments);
      if (WA.context != undefined)
      {
        if (xid)
          WA.context = oldcontext;
      }
      return ret;
    }

    if (eventnode != window && eventname == 'load' && browser.isMSIE) // special incompatible IE not firing onload event on images
    {
      thefunction = function(e)
      {
        if (this.readyState != 'complete' && this.readyState != 'loaded')
          return null;
        var xid = oldcontext = null;
        if (this.id && this.id.indexOf('|') != -1)
        {
          xid = WA.parseID(this.id);
          oldcontext = WA.context = xid[0] + '|' + xid[1] + '|';
        }
        var ret = eventfunction.apply(this, arguments);
        if (xid)
          WA.context = oldcontext;
        return ret;
      };
      eventnode.onreadystatechange = thefunction;
    }
    else if (eventnode.addEventListener)
    {
      if (eventname == 'mousewheel') // special incompatible mousewheel
      {
        eventnode.addEventListener('DOMMouseScroll', thefunction, eventcapture);
      }
      eventnode.addEventListener(eventname, thefunction, eventcapture);
    }
    else if (eventnode.attachEvent)
    {
      eventnode.attachEvent('on' + eventname, thefunction);
    }
    else
    {
      eventnode['on' + eventname] = thefunction;
    }
    events[eventnode.listeneruid][eventname][eventfunction.functionuid] = [eventnode, thefunction, eventcapture];
    return true;
  }

  // must be the SAME PARAMETERS as addListener
  this.removeListener = this.off = this.remove = this.stop = this.detachEvent = removeListener;
  function removeListener(eventname, eventnode, eventfunction, eventcapture)
  {
    if (typeof eventnode == 'string')
      eventnode = $(eventnode);
    if (!eventnode) // no node found ?
      return;
    //debug('eventManager.removeListener('+eventname+', '+eventnode.id+')');
    if (eventnode.listeneruid == undefined) // node not registered here
      return;
    if (eventfunction.functionuid == undefined) // function not registered here
      return;
    if (events[eventnode.listeneruid] == undefined) // already unregistered ?
      return;
    if (events[eventnode.listeneruid][eventname] == undefined) // already unregistered ?
      return;
    if (events[eventnode.listeneruid][eventname][eventfunction.functionuid] == undefined) // already unregistered ?
      return;

    if (eventname == 'load' && browser.isMSIE) // special incompatible IE not firing onload event
    {
      eventnode.onreadystatechange = nothing;
    }
    else if (eventnode.removeEventListener)
    {
      if (eventname == 'mousewheel')
      {
        eventnode.removeEventListener('DOMMouseScroll', events[eventnode.listeneruid][eventname][eventfunction.functionuid], eventcapture);
      }
      eventnode.removeEventListener(eventname, events[eventnode.listeneruid][eventname][eventfunction.functionuid], eventcapture);
    }
    else if (eventnode.detachEvent)
    {
      eventnode.detachEvent('on' + eventname, events[eventnode.listeneruid][eventname][eventfunction.functionuid]);
    }
    else
    {
      eventnode['on' + eventname] = null;
    }
    delete events[eventnode.listeneruid][eventname][eventfunction.functionuid];
    // *********************************************
    // do we clean the 3 levels of the array ?
  }

  // key is 'modif[+modif]+key'
  // modif is 'shift', 'alt', 'control' or 'ctrl'
  // key is 0-9, a-z, !@#$%^&*()_-+=}{]["';?><,./`~
  // can be also: special keys, arrows, functions etc. see the array in the
  // function.

  this.addKey = addKey;
  function addKey(key, callback)
  {
    //debug('eventManager.addKey('+key+')');
    var xkey = key.toLowerCase().split("+");
    for (var i = 0, l = xkey.length; i < l; i++)
    {
      if (xkey[i] == 'shift' || xkey[i] == 'control' || xkey[i] == 'ctrl' || xkey[i] == 'alt')
        continue;
      if (specialkeys[xkey[i]] != undefined)
        continue;
      // should be normal char, we take the 1rst one to be sure
      xkey[i] = xkey[i].charAt(0);
    }

    var data =
    {
      skey: key,
      key: xkey,
      callback: callback
    };
    keys.push(data);
  }

  this.removeKey = removeKey;
  function removeKey(key)
  {
    //debug('eventManager.removeKey('+key+')');
    keys.remove(key, 'skey');
  }

  /* private method */
  function keycallback(e)
  {
    var code = browser.getKey(e);
    var c = String.fromCharCode(code).toLowerCase();
    var shift = browser.ifShift(e);
    var ctrl = browser.ifCtrl(e);
    var alt = browser.ifAlt(e);
    for ( var i = 0, l=keys.length; i < l; i++)
    {
      // check any keys combination if ok
      var isok = 0;
      for ( var j = 0, m=keys[i]['key'].length; j < m; j++)
      {
        if (keys[i]['key'][j] == 'shift' && shift)
          isok++;
        else if (keys[i]['key'][j] == 'alt' && alt)
          isok++;
        else if ((keys[i]['key'][j] == 'control' || keys[i]['key'][j] == 'ctrl')
            && ctrl)
          isok++;
        else if (specialkeys[keys[i]['key'][j]] == code)
          isok++;
        else if (keys[i]['key'][j] === c)
          isok++;
      }
      if (isok == keys[i]['key'].length)
      {
        keys[i]['callback'](e, keys[i]['skey']);
      }
    }
  }

  this.registerBeforeFlush = registerBeforeFlush;
  function registerBeforeFlush(functionflush)
  {
    beforeflushs.push(functionflush);
  }

  this.registerFlush = registerFlush;
  function registerFlush(functionflush)
  {
    flushs.push(functionflush);
  }

  this.unregisterBeforeFlush = unregisterBeforeFlush;
  function unregisterBeforeFlush(functionflush)
  {
    beforeflushs.remove(functionflush);
  }

  this.unregisterFlush = unregisterFlush;
  function unregisterFlush(functionflush)
  {
    flushs.remove(functionflush);
  }

  /* private method */
  function _beforeflush(e)
  {
    // then call all flush for other managers
    var result = '';
    for ( var i = 0, l = beforeflushs.length; i < l; i++)
    {
      result += beforeflushs[i](e);
    }
    if (result != '')
    {
      WA.browser.cancelEvent(e);   // for ie, ff, chrome, safari
      e.returnValue = result;      // for ie, ff
      return result;               // for ie
    }
  }

  function _flush(e)
  {
    // then call all flush for other managers
    for ( var i = 0, l = flushs.length; i < l; i++)
    {
      flushs[i](e);
      flushs[i] = null;
    }

    // no way to block unload on other browsers, se we destroy
    for (i in events)
    {
      for (j in events[i])
      {
        for (k in events[i][j])
        {
          if (events[i][j][k][0])
          {
            if (j == 'mousewheel')
            {
              events[i][j][k][0].removeEventListener('DOMMouseScroll', events[i][j][k][1], events[i][j][k][2]);
            }
            events[i][j][k][0].removeEventListener(j, events[i][j][k][1], events[i][j][k][2]);
          }
          else if (events[i][j][k][0].detachEvent)
          {
            events[i][j][k][0].detachEvent('on' + j, events[i][j][k][1]);
          }
          else
          {
            events[i][j][k][0]['on' + j] = null;
          }
        }
      }
    }

    // we stop listening unload and keypress
    delete events;
    delete beforeflushs;
    delete flushs;
    delete keys;
    self = null;
  }

  // we take control of unload. blocks unload only works on
  // IE, FF, CHROME and SAFARI with beforeunload / should check version of those
  this.addListener('beforeunload', window, _beforeflush, false);
  this.addListener('unload', window, _flush, false);
  // we listen the key binder
  this.addListener('keydown', document, keycallback, false);
}

var eventManager = new _eventManager();
// rapid shortcuts
var on = eventManager.addListener;
var off = eventManager.removeListener;
var key = eventManager.addKey;
var keyon = eventManager.addKey;
var keyoff = eventManager.removeKey;




/* eventManager.js, WAJAF, the WebAbility(r) Javascript Application Framework
 (c) 2008 Philippe Thomassigny
 */


/*
function _eventManager() {
	var self = this;
	this.listenerid = 1;
	this.functionid = 1;
	this.events = {};
	this.flushs = [];
	this.keys = [];
	this.specialkeys = {
		'esc' :27,
		'escape' :27,
		'tab' :9,
		'space' :32,
		'return' :13,
		'enter' :13,
		'backspace' :8,
		'scrolllock' :145,
		'capslock' :20,
		'numlock' :144,
		'pause' :19,
		'break' :19,
		'insert' :45,
		'home' :36,
		'delete' :46,
		'end' :35,
		'pageup' :33,
		'pagedown' :34,
		'left' :37,
		'up' :38,
		'right' :39,
		'down' :40,
		'f1' :112,
		'f2' :113,
		'f3' :114,
		'f4' :115,
		'f5' :116,
		'f6' :117,
		'f7' :118,
		'f8' :119,
		'f9' :120,
		'f10' :121,
		'f11' :122,
		'f12' :123
	};

	// eventname: one of: mousedown, mouseup, click, dblclick, mousemove,
	// mouseover, mouseout, mousewheel,
	// keydown, keyup, keypress, load, unload, scroll,
	// focus, blur, change, submit, abort, error, reset, resize
	// eventnode: the id or the node itself
	// eventfunction: pointer to the function to execute when the event happens
	// eventcapture: true/false to notify if this event is greedy
	this.addListener = this.on = this.add = this.start = this.listen = this.attachEvent = this.registerEvent = addListener;
	function addListener(eventname, eventnode, eventfunction, eventcapture) {

		if (typeof eventnode == 'string')
			eventnode = $(eventnode);
		if (!eventnode) // no node found ?
			return false;

		// link the UID to the node
		if (eventnode.listeneruid == undefined)
			eventnode.listeneruid = self.listenerid++;
		if (eventfunction.functionuid == undefined)
			eventfunction.functionuid = self.functionid++;

		if (self.events[eventnode.listeneruid] == undefined)
			self.events[eventnode.listeneruid] = {};
		if (self.events[eventnode.listeneruid][eventname] == undefined)
			self.events[eventnode.listeneruid][eventname] = {};

		// get the context from the ID of the node if any
		eventnode.context = WA.context;

		thefunction = function() {
			var xid = oldcontext = null;
			if (this.id && this.id.indexOf('-') != -1) {
				xid = WA.parseID(this.id);
				oldcontext = WA.setContext(xid[0] + '-' + xid[1] + '-');
			}
			var ret = eventfunction.apply(this, arguments);
			if (xid)
				WA.setContext(oldcontext);
			return ret;
		}

		if (eventname == 'load' && browser.msie) // special incompatible IE
													// not firing onload event
		{
			thefunction = function() {
				if (this.readyState != 'complete'
						&& this.readyState != 'loaded')
					return null;
				var xid = oldcontext = null;
				if (this.id && this.id.indexOf('-') != -1) {
					xid = WA.parseID(this.id);
					oldcontext = WA.setContext(xid[0] + '-' + xid[1] + '-');
				}
				var ret = eventfunction.apply(this, arguments);
				if (xid)
					WA.setContext(oldcontext);
				return ret;
			};
			eventnode.onreadystatechange = thefunction;
		} else if (eventnode.addEventListener) {
			if (eventname == 'mousewheel') // special incompatible mousewheel
			{
				eventnode.addEventListener('DOMMouseScroll', thefunction,
						eventcapture);
			}
			eventnode.addEventListener(eventname, thefunction, eventcapture);
		} else if (eventnode.attachEvent) {
			eventnode.attachEvent('on' + eventname, thefunction);
		} else {
			eventnode['on' + eventname] = thefunction;
		}
		self.events[eventnode.listeneruid][eventname][eventfunction.functionuid] = thefunction;
		return true;
	}

	// must be the SAME PARAMETERS as addListener
	this.removeListener = this.off = this.remove = this.stop = this.detachEvent = removeListener;
	function removeListener(eventname, eventnode, eventfunction, eventcapture) {
		if (typeof eventnode == 'string')
			eventnode = $(eventnode);
		if (!eventnode) // no node found ?
			return;
		if (eventnode.listeneruid == undefined) // node not registered here
			return;
		if (eventfunction.functionuid == undefined) // function not registered
													// here
			return;
		if (self.events[eventnode.listeneruid] == undefined) // already
																// unregistered
																// ?
			return;
		if (self.events[eventnode.listeneruid][eventname] == undefined) // already
																		// unregistered
																		// ?
			return;
		if (self.events[eventnode.listeneruid][eventname][eventfunction.functionuid] == undefined) // already
																									// unregistered
																									// ?
			return;

		if (eventname == 'load' && browser.msie) // special incompatible IE
													// not firing onload event
		{
			eventnode.onreadystatechange = nothing;
		} else if (eventnode.removeEventListener) {
			if (eventname == 'mousewheel') {
				eventnode
						.removeEventListener(
								'DOMMouseScroll',
								self.events[eventnode.listeneruid][eventname][eventfunction.functionuid],
								eventcapture);
			}
			eventnode
					.removeEventListener(
							eventname,
							self.events[eventnode.listeneruid][eventname][eventfunction.functionuid],
							eventcapture);
		} else if (eventnode.detachEvent) {
			eventnode
					.detachEvent(
							'on' + eventname,
							self.events[eventnode.listeneruid][eventname][eventfunction.functionuid]);
		} else {
			eventnode['on' + eventname] = null;
		}
		delete self.events[eventnode.listeneruid][eventname][eventfunction.functionuid];
		// *********************************************
		// do we clean the 3 levels of the array ?
		return;
	}

	// key is 'modif[+modif]+key'
	// modif is 'shift', 'alt', 'control' or 'ctrl'
	// key is 0-9, a-z, !@#$%^&*()_-+=}{]["';?><,./`~
	// can be also: special keys, arrows, functions etc. see the array in the
	// function.

	this.keyListener = keyListener;
	function keyListener(key, callback, node, bubble) {
		var xkey = key.toLowerCase().split("+");
		if (node == undefined)
			node = document;
		if (bubble == undefined)
			bubble = false;
		for ( var i in xkey) {
			if (xkey[i] == 'shift' || xkey[i] == 'control' || xkey[i] == 'ctrl'
					|| xkey[i] == 'alt')
				continue;
			if (self.specialkeys[xkey[i]] != undefined)
				continue;
			// should be normal char, we take the 1rst one to be sure
			xkey[i] = xkey[i].charAt(0);
		}

		var data = {
			skey :key,
			key :xkey,
			callback :callback,
			node :node,
			bubble :bubble
		};
		self.keys.push(data);
		return;
	}

	this.keycallback = keycallback;
	function keycallback(e)
	{
		var code = browser.getKey(e);
		var c = String.fromCharCode(code).toLowerCase();
		var shift = browser.ifShift(e);
		var ctrl = browser.ifCtrl(e);
		var alt = browser.ifAlt(e);
		for ( var i in self.keys)
		{
		  if (typeof self.keys[i] == 'function') // protection IE bug
        continue;
		  if (typeof self.keys[i]['key'] == 'function')
        continue;
			// check any keys combination if ok
			var isok = 0;
			for ( var j in self.keys[i]['key']) {
  		  if (typeof self.keys[i]['key'][j] == 'function') // protection IE bug
          continue;
				if (self.keys[i]['key'][j] == 'shift' && shift)
					isok++;
				else if (self.keys[i]['key'][j] == 'alt' && alt)
					isok++;
				else if ((self.keys[i]['key'][j] == 'control' || self.keys[i]['key'][j] == 'ctrl')
						&& ctrl)
					isok++;
				else if (self.specialkeys[self.keys[i]['key'][j]] == code)
					isok++;
				else if (self.keys[i]['key'][j] === c)
					isok++;
			}
			if (isok == self.keys[i]['key'].length) {
				self.keys[i]['callback'](e, self.keys[i]['skey']);
			}
		}
		return;
	}

	this.registerFlush = registerFlush;
	function registerFlush(functionflush) {
		self.flushs.push(functionflush);
		return;
	}

	this.flush = flush;
	function flush() {
		return;
		for (i in self.events) {
			for (j in self.events[i]) {
				for (k in self.events[i][j]) {
					if (self.events[i][j][k][0]) {
						if (j == 'mousewheel') {
							self.events[i][j][k][0].removeEventListener(
									'DOMMouseScroll', self.events[i][j][k][2],
									self.events[i][j][k][3]);
						}
						self.events[i][j][k][0].removeEventListener(j,
								self.events[i][j][k][2],
								self.events[i][j][k][3]);
					} else if (self.events[i][j][k][0].detachEvent) {
						self.events[i][j][k][0].detachEvent('on' + j,
								self.events[i][j][k][2]);
					} else {
						self.events[i][j][k][0]['on' + j] = null;
					}
				}
			}
		}

		// then call all flush for other managers
		for ( var i = 0, l = self.flushs.length; i < l; i++) {
			self.flushs[i]();
			self.flushs[i] = null;
		}

		// we stop listening unload and keypress

		delete self.events;
		delete self.flushs;
		delete self.keys;
		self = null;
		return;
	}

	// we take control of unload
	this.addListener('unload', window, this.flush, false);
	// we listen the key binder
	this.addListener('keydown', document, this.keycallback, false);
}

var eventManager = new _eventManager();
// rapid shortcuts
var on = eventManager.addListener;
var off = eventManager.removeListener;
var key = eventManager.keyListener;

*/function windowTag(idtag, id, callback, manager)
{
  // we use a trick for functions since 'this' is NOT including 'var' !
  var self = this;
  this.myManager = manager;
  this.myCallback = callback;
  this.idtag = idtag;
  this.uid = '';
  this.status = false;

  this.getID = getID;
  function getID()
  {
    return self.uid;
  }

  this.show = show;
  function show()
  {
    $(self.uid).style.display = "";
    if (self.myCallback)
      self.myCallback(self.idtag, true);
  }

  this.hide = hide;
  function hide()
  {
    $(self.uid).style.display = "none";
    if (self.myCallback)
      self.myCallback(self.idtag, false);
  }

  this.switchTag = switchTag;
  function switchTag()
  {
    // hide others
    self.myManager.hideAll();
    self.show();
  }

  this.disable = disable;
  function disable()
  {
    dd = $(self.idtag);
    if (dd)
    {
      dd.onclick = null;
    }
  }

  this.enable = enable;
  function enable()
  {
    dd = $(self.idtag);
    if (dd)
    {
      dd.onclick = self.switchTag;
    }
  }

  // Constructor
  this.uid = id;
  if ($(this.uid) == null)
    return false;

  this.enable();

  this.status = false;
  return this;
}

function tagContainer()
{
  var self = this;
  this.debug = false;
  this.tags = new Array();
  this.myOpenClose = "";
  this.myCallbackClose = null;

  // **************************************************************************
  // PUBLIC METHODS
  // **************************************************************************

  this.registerTag = registerTag;
  function registerTag(idtag, iddivision, callback)
  {
    var tag = new windowTag(idtag, iddivision, callback, self);
    if (!tag)
      return null;
    self.tags[self.tags.length] = tag;
    return tag;
  }

  this.registerShowHideTag = registerShowHideTag;
  function registerShowHideTag(idtag, callback)
  {
    dd = $(idtag);
    if (dd)
    {
      dd.onclick = switchShowHideTag;
    }
    self.myOpenClose = idtag;
    self.myCallbackClose = callback;
  }

  this.switchShowHideTag = switchShowHideTag;
  function switchShowHideTag()
  {
    self.hideAll();
    self.myCallbackClose(self.myOpenClose);
  }

  this.hideAll = hideAll;
  function hideAll()
  {
    for (var i=0; i < self.tags.length; i++)
      self.tags[i].hide();
  }

  this.showTag = showTag;
  function showTag(id)
  {
    for (var i=0; i < self.tags.length; i++)
    {
      if (self.tags[i].idtag == id)
        self.tags[i].show();
    }
  }

  this.disableTag = disableTag;
  function disableTag(id)
  {
    for (var i=0; i < self.tags.length; i++)
    {
      if (self.tags[i].idtag == id)
        self.tags[i].disable();
    }
  }

  this.enableTag = enableTag;
  function enableTag(id)
  {
    for (var i=0; i < self.tags.length; i++)
    {
      if (self.tags[i].idtag == id)
        self.tags[i].enable();
    }
  }

  return this;
}

/* ajaxManager.js, WAJAF, the WebAbility(r) Javascript Application Framework
   (c) 2008 Philippe Thomassigny
*/

function ajaxRequest(url, method, data, feedback, autosend, listener)
{
  var self = this;
  // parameters
  this.url = url;
  this.method = method.toUpperCase();
  this.data = data;
  this.feedback = feedback;
  this.autosend = autosend;
  // special parameters
  this.period = 0;
  this.times = 0;
  this.timeoutabort = 0;        // time out to abort, no default, let it to the ajax autocontrol.
  this.statefeedback = null;    // consider waiting, error and abort feedbacks
  // working attributes
  this.request = null;
  this.parameters = null;
  this.timer = null;
  this.timerabort = null;
  this.state = 0;               // 0 = nothing, 1 = sent and waiting, 2 = finished, 3 = error
  this.listener = listener;


  try { this.request = new XMLHttpRequest(); }
  catch(e) {
    try { this.request = new ActiveXObject("Msxml2.XMLHTTP.3.0"); }
    catch(e) {
      try { this.request = new ActiveXObject("Msxml2.XMLHTTP"); }
      catch(e) {
        try { this.request = new ActiveXObject("Microsoft.XMLHTTP"); }
        catch(e) {
          alert("XMLHttpRequest not supported");
          return;
        }
      }
    }
  }

  this.callNotify = callNotify;
  function callNotify(event)
  {
    if (self.listener)
    {
      self.listener(event);
    }
  }

  // Special parameters
  this.setPeriodic = setPeriodic;
  function setPeriodic(period, times)
  {
    self.period = period;
    self.times = times;
    return;
  }

  this.addStateFeedback = addStateFeedback;
  function addStateFeedback(statefeedback, timeoutabort)
  {
    self.statefeedback = statefeedback;
    self.timeoutabort = timeoutabort;
    return;
  }

  // Parameters for POST/GET send
  this.addParameter = addParameter;
  function addParameter(id, value)
  {
    if (self.parameters === null)
      self.parameters = {};
    self.parameters[id] = value;
    return;
  }

  this.getParameters = getParameters;
  function getParameters()
  {
    var data = self.data || '';
    for (i in self.parameters)
      data += (data.length > 0?'&':'') + escape(i) + '=' + escape(self.parameters[i]);
    return data;
  }

  this.clearParameters = clearParameters;
  function clearParameters()
  {
    self.parameters = null;
    return;
  }

  // Ajax control
  this.headers = headers;
  function headers()
  {
    self.request.setRequestHeader('X-Requested-With', 'WAJAF::Ajax - WebAbility(r) v4');
    if (self.method == 'POST')
    {
      self.request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
      if (self.request.overrideMimeType)
        self.request.setRequestHeader("Connection", "close");
      self.request.setRequestHeader("Method", "POST " + self.url + " HTTP/1.1");
    }
    return;
  }

  this.send = send;
  function send()
  {
    if (self.timer)
      self.timer = null;
    if (self.request.readyState != 0 && self.request.readyState != 4) // still doing something
      return;

    self.request.onreadystatechange = self.process;
    if (self.timeoutabort)
      self.timerabort = setTimeout( function() { self.abort(); }, self.timeoutabort );
    try
    {
      var url = self.url;
      var parameters = self.getParameters();
      if (self.method == 'GET' && parameters.length > 0)
        url += (url.match(/\?/) ? '&' : '?') + parameters;
      self.request.open(self.method, url, true);
      self.headers();
      self.callNotify('start');
      self.request.send(self.method == 'POST' ? parameters : null);
      self.state = 1;
      debug('Enviando request AJAX: '+url, 2);
    }
    catch (e)
    {
      debug('Error creando request AJAX: '+url, 2);
      self.state = 3;
      self.processError(1, e);
    }
    return;
  }

  this.process = process;
  function process()
  {
    try
    {
      if (self.request.readyState == 4)
      {
        if (self.request.status == 200)
        {
          debug('Respuesta AJAX recibida: '+self.url, 2);
          if (self.timerabort)
          {
            window.clearTimeOut(self.timerabort);
            self.timerabort = null;
          }
          self.callNotify('stop');
          if (self.feedback)
          {
            self.feedback(self.request);
          }
          self.state = 2;
        }
        else
        {
          debug('Error en la respuesta AJAX recibida: '+self.url, 2);
          self.state = 3;
          // we call error feedback, or alert
          self.processError(3, "Error "+self.request.status+":\n" + self.request.statusText);
        }
        self.request.onreadystatechange = nothing;  // IE6 CANNOT assign null !!!
        var state = self.checkPeriod();
        if (!state)
          setTimeout( function() { ajaxManager.destroyRequest(self); }, 1);
      }
      else
      {
        self.waiting();
      }
    }
    catch(e)
    {
      debug('Error grave en la respuesta AJAX recibida: '+self.url, 2);
      self.state = 3;
      self.processError(2, e);
    }
    return;
  }

  this.checkPeriod = checkPeriod;
  function checkPeriod()
  {
    if (self.period)
    {
      if (self.times-- > 0)
      {
        self.timer = setTimeout( function() { self.send(); }, self.period);
        return true;
      }
    }
    return false;
  }

  this.waiting = waiting;
  function waiting()
  {
    // dispatcher for user events like "loading...", "making request", "sending information" based on readyState , etc ?
    // could also use a setInterval to periodically call this function to know how is going the call
    if (self.statefeedback)
      self.statefeedback('wait', self.request.readyState, '');
    return;
  }

  // any error
  // type = 1: error sending, 2: error during process, 3: error state != 200, 4: timeout forced
  this.processError = processError;
  function processError(type, error)
  {
    self.callNotify('error');
    if (typeof error == 'object')
      error = error.message;
    // abort and call feedback error
    if (self.statefeedback)
      self.statefeedback('error', type, error);
    //else
      //alert('Error: '+type+', '+error);
    return;
  }

  // we abort after a given timeout
  this.abort = abort;
  function abort()
  {
    self.timerabort = null;
    if (self.timer)
    {
      window.clearTimeOut(self.timer);
      self.timer = null;
    }
    self.processError(4, 'Timeout');
    self.request.abort();
    self.request.onreadystatechange = null;
    if (!self.checkPeriod())
      setTimeout( function() { ajaxManager.destroyRequest(self); }, 1);
    return;
  }

  this.destroy = destroy;
  function destroy()
  {
    if (self.timerabort)
    {
      window.clearTimeOut(self.timerabort);
      self.timerabort = null;
    }
    if (self.timer)
    {
      window.clearTimeOut(self.timer);
      self.timer = null;
    }
    if (self.state == 1 || self.state == 3)
    {
      self.abort();
    }
    self.request.onreadystatechange = nothing;
    self.clearParameters();
    delete self.request;
    self.statefeedback = null;
    self.feedback = null;
    self = null;
    return;
  }

  if (autosend)
    self.send();
  return;
}

function _ajaxManager()
{
  var self = this;
  this.requests = [];
  this.listener = null;

  this.setListener = setListener;
  function setListener(listener)
  {
    self.listener = listener;
  }

  this.callNotify = callNotify;
  function callNotify(event)
  {
    if (self.listener)
    {
      self.listener(event);
    }
  }

  this.destroyRequest = destroyRequest;
  function destroyRequest(r)
  {
    for (var i=0, l=self.requests.length; i < l; i++)
    {
      if (self.requests[i] == r)
      {
        self.requests[i].destroy();
        self.requests.splice(i, 1);
        self.callNotify('destroy');
        break;
      }
    }
    return;
  }

  this.createRequest = createRequest;
  function createRequest(url, method, data, feedback, dosend)
  {
    self.callNotify('create');
    var r = new ajaxRequest(url, method, data, feedback, dosend, self.listener);
    if (r)
    {
      self.requests.push(r);
    }
    return r;
  }

  this.createPeriodicRequest = createPeriodicRequest;
  function createPeriodicRequest(period, times, url, method, data, feedback, dosend)
  {
    self.callNotify('create');
    var r = new ajaxRequest(url, method, data, feedback, dosend, self.listener);
    if (r)
    {
      self.requests.push(r);
      r.setPeriodic(period, times);
    }
    return r;
  }

  this.destroy = destroy;
  function destroy()
  {
    self.listener = null;
    for (var i=0, l=self.requests.length; i < l; i++)
      self.requests[i].destroy();
    delete self.requests;
    self = null;
    return;
  }

  eventManager.registerFlush(self.destroy);
  return;
}

var ajaxManager = new _ajaxManager();
// shortcuts
var ajax = ajaxManager.createRequest;

/* animManager.js, WAJAF, the WebAbility(r) Javascript Application Framework
   (c) 2008 Philippe Thomassigny
*/

// individual sprites to anim
function animSprite(id, domNode, callback, script)
{
  var self = this;

  if (typeof domNode == 'string')
    this.domNode = $(domNode);
  else
    this.domNode = domNode;
  if (this.domNode == null)
    return null;

  this.id = id;
  this.callback = callback;
  this.script = script;

  this.timer = null;
  this.starttime = null;
  this.pointer = 0;

  this.getHex = getHex;
  function getHex(v)
  {
    if (v < 0 ) v = 0;
    if (v > 255) v = 255;
    var s = v.toString(16).toUpperCase();
    if (s.length < 2)
      s = '0' + s;
    return s;
  }

  this.start = start;
  function start()
  {
    self.starttime = new Date().getTime();
    self.pointer = 0;
    self.timer = setTimeout(self.anim, 10);
  }

  this.anim = anim;
  function anim()
  {
    clearTimeout(self.timer);
    self.timer = null;

    var time = new Date().getTime();
    var diff = time - self.starttime;
    var order = self.script.chain[self.pointer];
    if (order.calculate)
      order = order.calculate(diff, order);
    if (diff > order.time)
    {
      if (order.type == 'move')
      {
        if (order.xend != undefined)
          self.domNode.style.left = order.xend + 'px';
        if (order.yend != undefined)
          self.domNode.style.top = order.yend + 'px';
        if (order.wend != undefined)
          self.domNode.style.width = order.wend + 'px';
        if (order.hend != undefined)
          self.domNode.style.height = order.hend + 'px';
        if (order.rend != undefined)
          self.domNode.style.color = '#' + self.getHex(order.rend) + self.getHex(order.gend) + self.getHex(order.bend);
        if (order.brend != undefined)
          self.domNode.style.backgroundColor = '#' + self.getHex(order.brend) + self.getHex(order.bgend) + self.getHex(order.bbend);
        if (order.tend != undefined)
        {
          self.domNode.style.opacity = order.tend/100;
          self.domNode.style.filter = 'alpha(opacity: '+order.tend+')';
        }
      }
      self.pointer++;
      if (!self.script.chain[self.pointer])
      {
        if (!self.script.loop)
        {
          if (self.callback)
            self.callback('end');
          animManager.destroySprite(self.id);
          return;
        }
        self.pointer = 0;
        if (self.callback)
          self.callback('loop');
      }
      self.starttime = new Date().getTime() - diff + order.time; // start new cycle synchronized on last one
      self.timer = setTimeout(self.anim, 10);
    }
    else
    {
      if (order.type == 'wait')
      {
        self.timer = setTimeout(self.anim, order.time - diff );
        return;
      }
      if (order.xend != undefined)
      {
        var x = order.xinit + Math.ceil((order.xend-order.xinit)/order.time*diff);
        self.domNode.style.left = x + 'px';
      }
      if (order.yend != undefined)
      {
        var y = order.yinit + Math.ceil((order.yend-order.yinit)/order.time*diff);
        self.domNode.style.top = y + 'px';
      }
      if (order.wend != undefined)
      {
        var w = order.winit + Math.ceil((order.wend-order.winit)/order.time*diff);
        self.domNode.style.width = w + 'px';
      }
      if (order.hend != undefined)
      {
        var h = order.hinit + Math.ceil((order.hend-order.hinit)/order.time*diff);
        self.domNode.style.height = h + 'px';
      }
      if (order.rend != undefined)
      {
        var r = order.rinit + Math.ceil((order.rend-order.rinit)/order.time*diff);
        var g = order.ginit + Math.ceil((order.gend-order.ginit)/order.time*diff);
        var b = order.binit + Math.ceil((order.bend-order.binit)/order.time*diff);
        self.domNode.style.color = '#' + self.getHex(r) + self.getHex(g) + self.getHex(b);
      }
      if (order.brend != undefined)
      {
        var br = order.brinit + Math.ceil((order.brend-order.brinit)/order.time*diff);
        var bg = order.bginit + Math.ceil((order.bgend-order.bginit)/order.time*diff);
        var bb = order.bbinit + Math.ceil((order.bbend-order.bbinit)/order.time*diff);
        self.domNode.style.backgroundColor = '#' + self.getHex(br) + self.getHex(bg) + self.getHex(bb);
      }
      if (order.tend != undefined)
      {
        var t = order.tinit + Math.ceil((order.tend-order.tinit)/order.time*diff);
        self.domNode.style.opacity = t/100;
        self.domNode.style.filter = 'alpha(opacity: '+t+')';
      }
      self.timer = setTimeout(self.anim, 10);
    }
  }

  this.destroy = destroy;
  function destroy()
  {
    if (self.timer)
      clearTimeout(self.timer);
    self.timer = null;
    self.starttime = null;
    self.pointer = 0;
    self.id = null;
    self.callback = null;
    self.script = null;
    self.domNode = null;
    self = null;
  }

  if (script.autostart)
    this.start();
  return this;
}

function _animManager()
{
  var self = this;

  this.sprites = {};
  this.sequences = {};

  // script is {}
  // autostart: true/false
  // loop: true/false
  // chain: [] of {}:
  //    type: 'move', 'wait'
  //    metrics: xinit, xend, yinit, yend, winit, wend, hinit, hend, tinit, tend   => x,y: position, w,h: size, t: transparency,
  //             rinit, rend, ginit, gend, binit, bend, brinit, brend, bginit, bgend, bbinit, bbend   => red, green, blue & background
  //    time: time to do it in ms
  //    calculate: function to call instead of using position, size and transparency. will get back a metrics object
  // the sprite will be destroyed at the end of script except if loop = on
  this.createSprite = createSprite;
  function createSprite(id, domNode, callback, script)
  {
    if (self.sprites[id])
      return self.sprites[id];

    var sp = new animSprite(id, domNode, callback, script);
    if (sp)
      self.sprites[id] = sp;
    return sp;
  }

  this.fadein = fadein;
  function fadein(domNode, time, callback)
  {
    self.createSprite(domNode.id, domNode, callback, {autostart:true,loop:false,chain:[{type:'move',tinit:0,tend:100,time:time}]});
  }

  this.fadeout = fadeout;
  function fadeout(domNode, time, callback)
  {
    self.createSprite(domNode.id, domNode, callback, {autostart:true,loop:false,chain:[{type:'move',tinit:100,tend:0,time:time}]});
  }

  this.destroySprite = destroySprite;
  function destroySprite(id)
  {
    if (self.sprites[id])
    {
      self.sprites[id].destroy();
      delete self.sprites[id];
    }
  }

  // sequence is {} of sprites animations
  // loop: true/false
  // chain: [] of {}:
  //    type: 'show', 'hide', 'move', 'wait'
  //    domid: dom on which execute this order
  //    metrics: xinit, xend, yinit, yend, winit, wend, hinit, hend, tinit, tend => x,y: position, w,h: size, t: transparency
  //    time: time to do it in ms
  //    calculate: function to call instead of using position, size and transparency. will get back a metrics object
  //    wait: time to wait before executing next sequence order
  this.createSequence = createSequence;
  function createSequence(id, sequence)
  {

  }

  this.destroySequence = destroySequence;
  function destroySequence(id)
  {
    if (self.sequences[id])
    {
      // remove anything of this sequence
      delete self.sequences[id];
    }
  }

  // Sequences functions



  // flush
  this.destroy = destroy;
  function destroy()
  {
    for (var i in self.sprites)
    {
      self.sprites[i].destroy();
      delete self.sprites[i];
    }
    delete self.sprites;
    for (var i in self.sequences)
      delete self.sequences[i];
    delete self.sequences;
    self = null;
  }

  eventManager.registerFlush(self.destroy);
}

var animManager = new _animManager();
// shortcut
var anim = animManager.createSprite;
