/**
 *  base class of the library which is always required
 *
 *  @author  Tobias Hettinger
 *  @version $Id: wat.js 176 2012-01-17 08:00:46Z tobias $
 */


//=====================================================================================================================
//  information about the used browser
//=====================================================================================================================

//  get the browser identification
var nav = navigator.userAgent.toLowerCase();

//  create basic browser information
var WATBrowserInfo =
{
  IsIE     : nav.indexOf('msie') > -1 ? true : false,
  IsIE7    : nav.indexOf('msie 7') > -1 ? true : false,
  IsIE8    : nav.indexOf('msie 8') > -1 ? true : false,
  IsIE9    : nav.indexOf('msie 9') > -1 ? true : false,
  IsGecko  : nav.indexOf('gecko/') > -1 ? true : false,
  IsSafari : nav.indexOf('safari') > -1 ? true : false,
  IsOpera  : nav.indexOf('opera') > -1 ? true : false
}

//  create more detailed browser information
WATBrowserInfo.IsGeckoLike = WATBrowserInfo.IsGecko || WATBrowserInfo.IsSafari || WATBrowserInfo.IsOpera;


//=====================================================================================================================
//  data structures for the script inclusion
//=====================================================================================================================

/**
 *  constructor of the function execution descriptor
 */
var WATFuncExec = function(func, param)
{
  //  set the member variables
  this.func = func;
  this.param = param;
}


//=====================================================================================================================
//  global message box (e.g. to show ajax error messages)
//=====================================================================================================================

/**
 *  item descriptor for the global message box
 *  
 *  @param int    code     code of the message item
 *                         - 1 - AJAX error: the server does not respond
 *                         - 2 - AJAX error: a document was not found (error 404)
 *                         - 3 - AJAX error: server returned plain text instead of XML
 *  @param string key      key for the message which depends on the message code
 *                         - 1 - host name of the server (no path, no document)
 *                         - 2 - url of the document (no parameters)
 *                         - 3 - null, the key is unused
 *  @param string message  message to show
 */
var WATMessageBoxItem = function(code, key, message)
{
  //  member variables
  this.code = code;
  this.key = key;
  this.message = message;
  this.divElement = null;
}

/**
 *  global message box object (e.g. to show AJAX errors)
 */
var WATMessageBox = function()
{
  //  initialize the objects
  this.divElement = null;
  this.messages = new Array();
}

/**
 *  function to show a message in the message box
 *  
 *  @param int    code     code of the message item (-> see constructor of the message item)
 *  @param string key      key for the message which depends on the message code
 *  @param string message  message to show
 */
WATMessageBox.prototype.ShowMessage = function(code, key, message)
{
  var obj = this;
  
  //  event handler to close the message box
  function __HideMessageBox()
  {
    //  the user is allowed to close the message box but only
    //  if there are messages with code 3 (-> debug mode of the ajax manager)
    for (var index in obj.messages)
      if (obj.messages[index].code == 3)
        obj.divElement.style.display = 'none';
  }
  
  if (this.divElement == null)
  {
    //  the message box does not yet exist, create a new box
    this.divElement = document.createElement('div');
    this.divElement.className = 'wat_msg wat_shadow wat_font';
    this.divElement.style.position = 'absolute';
    this.divElement.ondblclick = __HideMessageBox;
    document.body.appendChild(this.divElement);
  }
  
  //  show the message box
  this.divElement.style.display = 'block';
  this.divElement.style.top = '10px';
  this.divElement.style.left = (watGetWindowWidth() - this.divElement.offsetWidth - 10) + 'px';  
  this.divElement.style.zIndex = WAT.GetNextZIndex();
  
  //  check if the message to show is already visible
  var canShow = true;
  for (var index in this.messages)
    if (key != null && this.messages[index].code == code && this.messages[index].key == key)
      canShow = false;
  
  if (canShow)
  {
    //  create the message
    var messageItem = new WATMessageBoxItem(code, key, message);
    messageItem.divElement = document.createElement('div');
    messageItem.divElement.className = 'wat_msg_item';
    messageItem.divElement.innerHTML = message;
    this.messages.push(messageItem);    
    
    //  show the message
    if (this.divElement.firstChild)
      this.divElement.insertBefore(messageItem.divElement, this.divElement.firstChild);
    else this.divElement.appendChild(messageItem.divElement);
  }
}

/**
 *  function to hide the message with the passed code and message key,
 *  hide the message box if there is no message to show
 */
WATMessageBox.prototype.HideMessage = function(code, key)
{
  for (var index in this.messages)
    if (this.messages[index].code == code && this.messages[index].key != null && this.messages[index].key == key)
    {
      //  the message has been found, remove the message from the message box and from the message array
      if (this.messages[index].divElement) this.divElement.removeChild(this.messages[index].divElement);
      this.messages.splice(index, 1);
    }
  
  //  hide the message box if there is no message to show
  if (this.divElement && this.messages.length == 0)
    this.divElement.style.display = 'none';
}


//=====================================================================================================================
//  global info box (e.g. to show information if a command failed or succeeded)
//=====================================================================================================================

/**
 *  item descriptor for the global info box
 *  
 *  @param int    code     code of the info item
 *                         - 1 - message is a notice
 *                         - 2 - the message is a success message
 *                         - 3 - the message is an error message
 *  @param string message  message to show
 *  @param int    timeout  timeout in milliseconds or 0/null to use the default timeout
 */
var WATInfoBoxItem = function(code, message, timeout)
{
  var obj = this;
 
  //  event handler that is called when the message times out
  function __OnTimeout() { if (obj.onTimeout) obj.onTimeout(obj); }
  
  //  member variables
  this.code = code;
  this.message = message;
  this.divElement = null;
  this.onTimeout = null;
  
  //  setup the timer
  if (!timeout || timeout < 1000) timeout = 1800;
  this.timeoutHandler = setTimeout(__OnTimeout, timeout);
}

/**
 *  function to update an info box item
 *  
 *  the function is able to change the code of the information and updates the timeout.
 *  It is not possible to update the message of the item.
 */
WATInfoBoxItem.prototype.Update = function(code, timeout)
{
  var obj = this;
  
  //  event handler that is called when the message times out
  function __OnTimeout() { if (obj.onTimeout) obj.onTimeout(obj); }
  
  //  update the item and setup a new timer 
  this.code = code;
  clearTimeout(this.timeoutHandler);  
  if (!timeout || timeout < 1000) timeout = 1800;
  this.timeoutHandler = setTimeout(__OnTimeout, timeout);
}


/**
 *  global info box object to show notices, success- and error messages
 */
var WATInfoBox = function()
{
  //  initialize the objects
  this.divElement = null;
  this.messages = new Array();
}

/**
 *  function to update the css class of the info box which depends on the shown messages
 */
WATInfoBox.prototype.UpdateCssClass = function()
{
  if (this.divElement)
  {
    var maxCode = 1;
    var className = 'wat_info wat_shadow wat_font ';
    for (var index in this.messages)
      if (this.messages[index].code > maxCode) maxCode = this.messages[index].code;
    
    switch(maxCode)
    {
      //  there are only notices
      case 1 :
        className += 'wat_info_notice';
        break;
        
      //  there is at least one success message but no error message
      case 2 :
        className += 'wat_info_success';
        break;
        
      //  there is at least one error message
      case 3 :
        className += 'wat_info_error';
        break;
    }
    
    //  apply the class
    this.divElement.className = className;
  }
}

/**
 *  function to show a notice, a succcess- or an error message
 *  
 *  @param int    code     message code
 *  @param string message  message to show
 *  @param int    timeout  timeout in milliseconds or 0/null to use the default timeout
 */
WATInfoBox.prototype.ShowInfo = function(code, message, timeout)
{
  var obj = this;
  
  //  event handler that is called when a message times out
  function __OnMessageTimeout(sender)
  {
    for (var index in obj.messages)
      if (obj.messages[index] == sender)
      {
        //  the message has been found, remove the message from the message box and from the message array
        if (sender.divElement) obj.divElement.removeChild(sender.divElement);
        obj.messages.splice(index, 1);
      }
    
    //  hide the message box if there is no message to show
    if (obj.divElement && obj.messages.length == 0)
      obj.divElement.style.display = 'none';
    else obj.UpdateCssClass();
  }
  
  //  check if a message item is visible with exactly the same message
  var item = null;
  for (var i in this.messages)
    if (this.messages[i].message == message)
      item = this.messages[i];
  
  if (item)
  {
    //  the message is already visible, simply update the message item and reset the timer
    item.Update(code, timeout);
    this.UpdateCssClass();
  }
  else
  {
    //  create a new message item
    var messageItem = new WATInfoBoxItem(code, message, timeout);
    messageItem.divElement = document.createElement('div');
    messageItem.divElement.className = 'wat_info_item';
    messageItem.divElement.innerHTML = message;
    messageItem.onTimeout = __OnMessageTimeout;
    this.messages.push(messageItem);   
    
    //  create the message box if it does not yet exist
    if (this.divElement == null)
    {
      //  the message box does not yet exist, create a new box
      this.divElement = document.createElement('div');
      this.divElement.style.position = 'absolute';
      document.body.appendChild(this.divElement);
    }
    
    //  show the message box
    this.UpdateCssClass();
    this.divElement.style.display = 'block';
    this.divElement.style.top = '8px';
    this.divElement.style.left = ((watGetWindowWidth() - this.divElement.offsetWidth) / 2) + 'px';  
    this.divElement.style.zIndex = WAT.GetNextZIndex(); 
    
    //  show the message
    if (this.divElement.firstChild)
      this.divElement.insertBefore(messageItem.divElement, this.divElement.firstChild);
    else this.divElement.appendChild(messageItem.divElement);
  }
}


//=====================================================================================================================
//  data structure for the control registry
//=====================================================================================================================

/**
 *  constructor of the control data descriptor
 */
var WATControlData = function(key, control)
{
  //  set the member variables
  this.control = control;
  this.key = key;  
}


//=====================================================================================================================
//  WAT Base Controller
//=====================================================================================================================

/**
 *  constructor of the WAT base class
 */
var WATBase = function()
{
  //  initialize the member variables
  this.messageBox = new WATMessageBox();
  this.infoBox = new WATInfoBox();
  this.enableProgressBar = true;  
  this.controlRegistry = new Array();
  this.controlIndex = 0;
  this.baseUrl = '';
  this.style = 'default';
  this.includeCount = 0;
  this.includedCount = 0;
  this.maxIncludeCount = 0;
  this.execStack = new Array();
  this.zindex = 1000;
  this.mouseX = 0;
  this.mouseY = 0;
  var obj = this;
  
  //  try to determine the base url
  var scripts = document.getElementsByTagName('script');
  for (var i in scripts)
    if (typeof (scripts[i].src) != 'undefined')
      if (scripts[i].src.match(/wat\/basic\/wat.js/))
        this.baseUrl = scripts[i].src.replace(/basic\/wat.js/, '');
 
  //  create the progress bar for file inclusions
  this.progressBar = document.createElement('div');
  this.progressBar.style.position = 'absolute';
  this.progressBar.style.display = 'none';
  this.progressBar.style.top = '30px';
  this.progressBar.style.left = '30px';
  this.progressBar.style.width = '150px';
  this.progressBar.style.height = '20px';
  this.progressBar.style.border = '1px solid';
  this.progressBar.style.borderColor = '#233241';
  this.progressBar.style.backgroundColor = '#FFFFFF';
  this.progressBar.style.zIndex = 9999999;
  
  //  append the progress bar to the document
  function __CreateProgressBar() { document.body.appendChild(obj.progressBar); } 
  if (WATBrowserInfo.IsIE) window.attachEvent('onload', __CreateProgressBar);
  else window.addEventListener('load', __CreateProgressBar, false);
  
  //  capture the mouse movement
  function __Mouse(event)
  {
    if (WATBrowserInfo.IsIE)
    {
      var scrollPos = obj.GetScrollTop();
      obj.mouseX = event.x + (document.body ? document.body.scrollLeft : 0);
      obj.mouseY = event.y + scrollPos;
    }
    else
    {
      obj.mouseX = event.pageX;
      obj.mouseY = event.pageY;
    }
  }
  if (WATBrowserInfo.IsIE) document.attachEvent('onmousemove', __Mouse);
    else document.addEventListener('mousemove', __Mouse, false);
}


//---------------------------------------------------------------------------------------------------------------------
//  style management

/**
 *  function to get the base url of the selected style
 *  
 *  @return string  the function returns the base url of the selected style with a trailing path spearator
 */
WATBase.prototype.GetStyleUrl = function()
{
  return this.baseUrl + 'styles/' + this.style + '/';
}

/**
 *  function to load the css file with the passed url
 *  
 *   @param string url  url of the css file to include
 */
WATBase.prototype.IncludeStyle = function(url)
{
  //  check if the css file has already been loaded
  var styles = document.getElementsByTagName('link');  
  var found = false;
  for (var i in styles)
    if (styles[i].href == url) found = true;

  if (!found)
  {
    //  the css file was not loaded yet
    var styleElement = document.createElement('link');
    styleElement.rel = 'stylesheet';
    styleElement.type = 'text/css';
    styleElement.href = url;
    styleElement.media = 'screen,print';
    document.getElementsByTagName('head')[0].appendChild(styleElement); 
  }
}

/**
 *  function to get the zIndex that is required to show an element on top of all other elements
 */
WATBase.prototype.GetNextZIndex = function()
{
  //  just increasing the zIndex counter is very simple but it is enough so far
  return this.zindex++;
}


//---------------------------------------------------------------------------------------------------------------------
//  global message management

/**
 *  function to show a message in the global message box
 */
WATBase.prototype.ShowMessage = function(code, key, message)
{
  //  show the message
  this.messageBox.ShowMessage(code, key, message);
}

/**
 *  frunction to remove a message from the global message box  
 */
WATBase.prototype.HideMessage = function(code, key)
{
  //  hide the message
  this.messageBox.HideMessage(code, key);
}


//---------------------------------------------------------------------------------------------------------------------
//  global information management

/**
 *  function to show a message in the global info box
 */
WATBase.prototype.ShowInfo = function(code, message, timeout)
{
  var obj = this;
  
  //  event handler to show the information
  function __ShowInfo() { obj.infoBox.ShowInfo(code, message, timeout); }
  
  //  show the message even if another function is still running
  setTimeout(__ShowInfo, 10);
}


//---------------------------------------------------------------------------------------------------------------------
//  input control registry

/**
 *  function to register the passed control in the global control registry
 *  
 *  @param object control  control to register (e.g. WATContextMenu)
 *  
 *  @return WATControlData  the function returns the control data descriptor of the control registry
 */
WATBase.prototype.RegisterControl = function(control)
{
  //  check if the control has already been registered
  for (var i in this.controlRegistry)
    if (this.controlRegistry[i].control == control)
      return this.controlRegistry[i];
  
  //  the control has not been registered yet
  var controlData = new WATControlData('c' + this.controlIndex, control);
  this.controlRegistry.push(controlData);
  
  //  return the created registry data item
  this.controlIndex++;  
  return controlData;
}

/**
 *  function to remove the control with the passed key from the control registry
 *  
 *  @param string key  key of the control to delete
 */
WATBase.prototype.RemoveControl = function(key)
{
  //  search the control with the passed key and remove it from the registry
  for (var i in this.controlRegistry)
    if (this.controlRegistry[i].key == key)  
      this.controlRegistry.splice(i, 1);
}

/**
 *  function to get the control with the passed key
 *  
 *  @param string key  key of the requested control
 *  
 *  @return WATControlData  the function returns the control descriptor with the
 *                          passed key or null if the control was not found
 */
WATBase.prototype.GetControl = function(key)
{
  //  search the control with the passed key
  for (var i in this.controlRegistry)
    if (this.controlRegistry[i].key == key)
      return this.controlRegistry[i];
  
  //  the control was not found
  return null;
}


//---------------------------------------------------------------------------------------------------------------------
//  mouse position

/**
 *  function to get the vertical scroll position in pixels of the current document
 */
WATBase.prototype.GetScrollTop = function()
{
  //  get the scroll position
  if (typeof window.pageYOffset != 'undefined')
    return window.pageYOffset;
  else
  {
    if (typeof document.compatMode != 'undefined' && document.compatMode != 'BackCompat')
      return document.documentElement.scrollTop;
    else
      if (typeof document.body != 'undefined')
        return document.body.scrollTop;
  }
        
  //  the scroll position could not be determined
  return 0;
}

/**
 *  function to get the X value of the current mouse position
 */ 
WATBase.prototype.GetMouseX = function() { return this.mouseX; }

/**
 *  function to get the Y value of the current mouse position
 */
WATBase.prototype.GetMouseY = function() { return this.mouseY; }


//---------------------------------------------------------------------------------------------------------------------
//  script inclusion

/**
 *  function to include the script file with the passed url
 *  
 *  @param string   url     URL of the javascript file to include
 *  @param function func    function (pointer or function name as string) to execute after the script has been loaded
 *  @param object   param   optional parameter of the function (only one parameter is allowed)
 *  @param bool     single  when calling the function in single mode, the function is directly executed after it
 *                          is available, otherwise the function is kept in the execution stack and is executed
 *                          after all scripts have been loaded
 */
WATBase.prototype.IncludeScript = function(url, func, param, single)
{
  //  check if the script has already been loaded
  var scripts = document.getElementsByTagName('script');
  var execCounter = 0;  
  var found = false;
  for (var i in scripts)
    if (scripts[i].src == url) found = true;

  //  function to execute the passed function if the function is in single mode
  function __execute()
  {
    var executed = false;
    execCounter++;
    
    //  try to execute the function if it is available
    if (typeof(func) == 'function') { executed=true; func(param); } 
      else if (typeof(window[func]) == 'function') { executed=true; window[func](param); }
    
    //  wait a moment if the function is not yet available
    if (!executed && execCounter < 50) setTimeout(__execute, 250);
  }
  
  //  function to load the requested script file
  function __load()
  {
    //  the script was not loaded yet
    var scriptElement = document.createElement('script');
    scriptElement.type = 'text/javascript';
    scriptElement.src = url;
    document.getElementsByTagName('head')[0].appendChild(scriptElement);
  }
  
  if (!found)
  {
    //  wait a moment before the file is inserted into the document tree to be sure that
    //  the inclusion of all required files is scheduled 
    setTimeout(__load, 500);
    
    if (!single)
    {
      //  do not modify these values if the function works in single mode
      this.includeCount++;
      this.maxIncludeCount++;
      this.IncludeProgressBar();
    }
  }

  //  execute the passed function or put it on the execution stack
  if (func)
  {  
    //  try to execute the function directly
    if (single) __execute();  
    else
    {
      //  put the function on the execution stack if there is more than one script to load
      if (this.includeCount > this.includedCount) this.execStack.push(new WATFuncExec(func, param)); 
      else
      {
        //  try to execute the function
        if (typeof(func) == 'function') func(param); 
          else if (typeof(window[func]) == 'function') window[func](param);
      }
    }
  }
}

/**
 *  function to include a WAT library file
 *  
 *  @param string   fileName  name of the library file to include (relative path from the WAT base url)
 *  @param function func      function (pointer) to execute after the script has been loaded
 */
WATBase.prototype.IncludeLibraryScript = function(fileName, func)
{
  //  complete the url and include the library file
  this.IncludeScript(this.baseUrl + fileName, func);
}

/**
 *  function to show the progress bar
 */
WATBase.prototype.IncludeProgressBar = function()
{
  //  update the progress bar
  this.progressBar.innerHTML = '<div style=" background:#6083A9; background: -webkit-gradient(linear, left top, ' +
         'left bottom, from(#80A4C4), to(#6083A9)); background: -moz-linear-gradient(top,  #80A4C4,  #6083A9); ' +
         'filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=\'#80A4C4\', endColorstr=\'#6083A9\'); ' +
         'width:' + (150 - ((this.includeCount - this.includedCount) / this.maxIncludeCount * 150)) +
         'px; height:20px;">&nbsp;</div>';
  
  //  show the progress bar
  if (this.enableProgressBar && this.progressBar.style.display == 'none')
    this.progressBar.style.display = 'block';
}

/**
 *  function that has to be called by an included javascript file in order to notify that the code is available 
 */
WATBase.prototype.ScriptIncluded = function()
{
  var obj = this;
  function execute()
  {
    //  includes in the header of the document may not decrement the include counter
    if (obj.includeCount > obj.includedCount) obj.includedCount++;
  
    //  execute the functions from the execution stack if all javascript files to include are available
    if (obj.includedCount > 0 && obj.includedCount == obj.includeCount)
    {
      //  remove the progress bar
      if (obj.progressBar) obj.progressBar.style.display = 'none';
      obj.maxIncludeCount = 0;
      
      for (var i in obj.execStack)
      {
        //  try to execute the function
        var funcExec = obj.execStack[i];
        if (typeof(funcExec.func) == 'function') funcExec.func(funcExec.param); 
        else if (typeof(window[funcExec.func]) == 'function') window[funcExec.func](funcExec.param);
        
        //  delete the execution descriptor
        delete obj.execStack[i];
      }
    }
    else
    {
      //  including has not been finished yet, update the progress bar
      if (obj.includeCount > 0) obj.IncludeProgressBar();
    }
  }

  //  try to synchronize inclusion (hope this works)
  //  http://ejohn.org/blog/how-javascript-timers-work/
  setTimeout(execute, 500);
}

//  create the WebApplicationToolkit
var WAT = new WATBase();


//=====================================================================================================================
//  useful functions that are supposed to be used in each script that uses the WAT
//  so it does not make sense to keep them in a separate file
//=====================================================================================================================

/**
 *  function to get the width of the browser window
 *  
 *  @return int  the function returns the width of the browser window in pixels
 */
function watGetWindowWidth()
{
  if (WATBrowserInfo.IsIE && !WATBrowserInfo.IsIE7 && !WATBrowserInfo.IsIE8 && !WATBrowserInfo.IsIE9)
    return document.body.clientWidth;
  else return document.documentElement.clientWidth;
}

/**
 *  function to get the height of the browser window
 *  
 *  @return int  the function returns the height of the browser window in pixels
 */
function watGetWindowHeight()
{
  if (WATBrowserInfo.IsIE && !WATBrowserInfo.IsIE7 && !WATBrowserInfo.IsIE8 && !WATBrowserInfo.IsIE9)
    return document.body.clientHeight;
  else return document.documentElement.clientHeight;
}

/**
 *  function to get the current style of an element
 *  
 *  @param object element       element to use
 *  @param string property      name of the required property (e.g. border-top-width)
 *  @param string valueFormat   required format of the value (['int'])
 *  @param mixed  defaultValue  value which is returned if the style is not readable or does
 *                              not have the requested format
 */
function watGetElementStyle(element, property, valueFormat, defaultValue)
{
  var result = defaultValue;
  var computedResult = null;
  
  try
  {
    //  try to get the property of the passed element
    computedResult = getComputedStyle(element, '').getPropertyValue(property);
  }
  catch(e)
  { 
    //  for Internet Explorer 7 and Internet Explorer 8
    if (element.currentStyle)
    {
      property = property.replace(/\-(\w)/g, function (strMatch, p1) { return p1.toUpperCase(); });
      computedResult = element.currentStyle[property];
    }
  }
  
  if (computedResult)
  {
    switch(valueFormat)
    {
      //  try to get an integer value
      case 'int' :
        computedResult = computedResult.replace(/px/g, '');
        computedResult = computedResult.replace(/ /g, ''); 
        if (watIsNumeric(computedResult)) result = watRound(watParseFloat(computedResult), 0);
          else result = defaultValue;
        break;
    
      //  return the value as it is
      default :
        result = computedResult;
        break;
    }
  }
  
  //  return the current style of the passed element or the default value
  //  if the style is not readable or does not have the requested format
  return result;
}

/**
 *  function to set inner HTML code for the passed element that is contained in the passed XML structure
 *  
 *  @param object element  element which content is to be set
 *  @param object node     node of the XML structure which contains the HTML content to set
 *  
 *  @return bool  the function returns true if the content that has been set was not empty or false if
 *                no content has been set or if the new content was empty
 */
function watInnerHtmlFromXML(element, node)
{
  if (element && node)
  {  
    var content = null;
    var xml = node.firstChild;
    while(xml)
    {
      switch(xml.nodeName)
      {
        //  content for the element
        case 'c' :
          if (xml.firstChild && xml.firstChild.data)
            if (content == null) content = xml.firstChild.data;
              else content += xml.firstChild.data;
          break;
      }
    
      //  the XML structure can contain more than one content node
      xml = xml.nextSibling;
    }

    //  apply the content
    if (content != null && content != '')
    {
      //  the new content is not empty
      element.innerHTML = watDecodeResponse(content);
      return true;
    }
    else element.innerHTML = '';
  }
  
  //  content was empty or no content has been set
  return false;
}

/**
 *  function to format a number
 *  
 *  the function is taken from http://phpjs.org/functions/number_format:481
 *  
 *  @param string value               value to format (can be numeric or a string)
 *  @param int    decimals            number of decimals
 *  @param string decimalSeparator    optional decimal separator (default ',')
 *  @param string thousandsSeparator  optional thousands separator (default '.')
 */
function watNumberFormat(value, decimals, decimalSeparator, thousandsSeparator)
{
  if (watIsNumeric(value))
  {
    //  a string is required
    if (typeof(value) == 'string') value = value.replace(',', '.');
      else value = value.toString(10);   
    value = value.replace(/[^0-9+\-Ee.]/g, '');
    
    //  check the parameters and initialize the variables
    var n = !isFinite(+value) ? 0 : +value;
    var prec = !isFinite(+decimals) ? 0 : Math.abs(decimals);
    var sep = (typeof(thousandsSeparator) === 'undefined') ? '.' : thousandsSeparator;
    var dec = (typeof(decimalSeparator) === 'undefined') ? ',' : decimalSeparator;
    var result = '';
    
    //  format the value, fix for IE parseFloat(0.55).toFixed(0) = 0;
    toFixedFix = function (n, prec) { var k = Math.pow(10, prec); return '' + Math.round(n * k) / k; };    
    result = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.');
    if (result[0].length > 3) { result[0] = result[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, sep); }
    if ((result[1] || '').length < prec)
    {
      result[1] = result[1] || '';
      result[1] += new Array(prec - result[1].length + 1).join('0');
    }
    
    //  finalize the result
    return result.join(dec);
  }
  //  the passed value is no number
  else return null;
}

/**
 *  function to check if the passed string can be parsed into a float value
 *  
 *  @param string value  string to check
 */
function watIsNumeric(value)
{
  //  initialize the variables
  var validChars = '0123456789.,-';
  
  //  do nothing if a number has already been passed or the passed value is null
  if (typeof(value) == 'int' || typeof(value) == 'float') return true;
  if (value == null) return false;
  
  //  an empty string can never be numeric
  if (value.length == 0) return false;
  
  //  test if the passed value consists of valid characters listed above
  for (var i=0; i<value.length; i++)
    if (validChars.indexOf(value.charAt(i)) == -1)
      return false;

  //  no invalid character was found, the value must be numeric
  return true;
}

/**
 *  function to parse a float value from the passed string
 *  
 *  @param string value  string to parse
 */
function watParseFloat(value)
{
  //  do some error checks
  if (!watIsNumeric(value)) return null;
  
  //  prepare and parse the value
  if (typeof(value) == 'string') value = value.replace(',', '.');
  return parseFloat(value);
}

/**
 *  function to round the passed value with a specified number of digits
 *  
 *  @param string|float value   value to round (can be a string)
 *  @param int          digits  number of decimal digits 
 */
function watRound(value, digits)
{
  //  get a numeric data type first
  value = watParseFloat(value);
  return Math.round(value * Math.pow(10, digits)) / Math.pow(10, digits);
}

/**
 *  function to disable a HMTL element for a certain time (e.g. to protect a button from being double clicked)
 *  
 *  @param object element  HTML element to disable
 *  @param int    timeout  time in miliseconds after that the element is enabled again. If no time
 *                         is passed, the default timeout is used
 */
function watDisableElement(element, timeout)
{
  if (!timeout) timeout = 2000;
  
  //  event handler to enable the element
  function __Enable() { element.disabled = false; }
  
  //  disable the element
  element.disabled = true;
  setTimeout(__Enable, timeout);
}

/**
 *  function to decode the passed string which has been received as an AJAX response
 */
function watDecodeResponse(value)
{
  if (value)
  {
    value = value.replace(/§!!§/g, '\"');
    value = value.replace(/§!§/g, '\'');  
    value = value.replace(/§!-/g, '<');
    value = value.replace(/-!§/g, '>');   
    value = value.replace(/§!and!§/g, '&');
    value = value.replace(/§!n!§/g, '\n');
    value = value.replace(/§!r!§/g, '\r');
  }
  
  //  return the decoded response
  return value;
}

