/* IBE Common-always-loaded utilities. */

var KEY_LEFT = 0x01;
var KEY_UP = 0x02;
var KEY_RIGHT = 0x04;
var KEY_DOWN = 0x08;
var KEY_LSHIFT = 0x10;
var KEY_CTRL = 0x11;
var KEY_ESC = 27;
var KEY_DEL = 46;
var KEY_BACKSPACE = 8;

var __logAlertLimit = 5;
var __logAlertCounter = 0;

var EVENT_KEYDOWN = "keydown";
var EVENT_KEYUP = "keyup";
var EVENT_KEYPRESS = "keypress";

var TYPE_OBJECT = "object";
var TYPE_FUNCTION = "function";
var TYPE_NUMBER = "number";
var TYPE_STRING = "string";

function clearField(f) {
  if (f.value == f.defaultValue) {
    f.value = "";
  }
}
function checkField(f) {
  if (f.value == "") {
    f.value = f.defaultValue;
  }
}

function getObj(id) {
  if (id == undefined) return null;
  var obj = null;
  if (document.getElementById) {
    obj = document.getElementById(id);
  } else {
    if (document.all) {
      obj = document.all[id];
    }
  }
  return obj;
}

function getObjsByName(id) {
  if (id == undefined) return null;
  return document.getElementsByName(id);
}

function getObjByName(id) {
  if (id == undefined) return null;
  var e = document.getElementsByName(id);
  if (e.length > 0)  return e[0];
  return null;
}

function getObjByIdThenName(id) {
  var e = getObj(id);
  if (e == null) e = getObjByName(id);
  return e;
}

function getEnabledObjByName(id) {
  if (id == undefined) return null;
  var es = document.getElementsByName(id);
  for (var i = 0; i < es.length; i++) {
    var e = es[i];
    if (!e.disabled) return e;
  }
  return null;
}

function getEnabledObjById(id) {
  if (id == undefined) return null;
  var e = document.getElementById(id);
  if (!e.disabled) return e;
  return null;
}

function toggleLayer(whichLayer, show) {
  if (document.getElementById) {
    var obj = getObj(whichLayer);
    if (obj) obj.style.display = show ? "" : "none";
  }
}

function toggleId(id) {
  if (document.getElementById) {
    var obj = getObj(id);
    var show = obj.style.display == "none";
    if (obj) obj.style.display = show ? "" : "none";
  }
}

function activate(id, isActive) {
  var obj = getObj(id);
  if (obj != null) {
    obj.readOnly = !isActive;
    obj.disabled = !isActive;
  }
}

function replaceHtml(e, html) {
  var obj = getObj(e);
  if (!obj) return;
  obj.innerHTML = html;
}

function showHide(e, show, setDisplay) {
  if (show) {
    setVisible(e, setDisplay);
  } else {
    setHidden(e, setDisplay);
  }
  return getObj(e);
}

function toggleVisible(elementId) {
  var obj = getObj(elementId);
  if (obj) {
    var s = obj.style;
    if (s.display == '' || s.display == 'visible') {
      setHidden(elementId, true);
    } else {
      setVisible(elementId, true);
    }
  }
}

function setVisible(elementID, setDisplay) {
  // If second arg is true then set style.display to apropriate value depending on type
  var obj = getObj(elementID);
  if (obj != null) {
    if (setDisplay) {
      obj.style.display = '';
    }
    obj.style.visibility = 'visible';
  }
}

function setVisibleObj(obj, setDisplay) {
  // If second arg is true then set style.display to apropriate value depending on type
  if (obj != null) {
    if (setDisplay) {
      obj.style.display = '';
    }
    obj.style.visibility = 'visible';
  }
}

function setHidden(elementID, setDisplay) {
  // If second arg is true then set style.display to 'none'
  var obj = getObj(elementID);
  if (obj != null) {
    obj.style.visibility = 'hidden';
    if (setDisplay) {
      obj.style.display = 'none';
    }
  }
}

function setHiddenObj(obj, setDisplay) {
  // If second arg is true then set style.display to 'none'
  if (obj != null) {
    obj.style.visibility = 'hidden';
    if (setDisplay) {
      obj.style.display = 'none';
    }
  }
}

function displayFor(e) {
  var tn = e.tagName.toUpperCase();
  var t = 'block';
  if (tn == 'TR')t = 'table-row';
  if (tn == 'TD')t = 'table-cell';
  if (tn == 'TABLE')t = 'table';
  return t;
}

function winStat(s) {
  window.status = s;
  return true;
}

function omo(e, isOver, cssClass) {
  var cl = e.className;
  if (isOver) {
    e.style.cursor = 'pointer';
    if (cssClass) e.className = (cl ? cl : '') + '___xxx ' + cssClass;
  } else {
    e.style.cursor = 'default';
    if (cssClass) e.className = cl.replace('___xxx ' + cssClass, '');
  }
}

function getPosition(o) {
  var start = getSelectionStart(o);
  if (start == getSelectionEnd(o)) {
    return start;
  } else {
    return -1;
  }
}

function setPosition(o, p) {
  if (o.createTextRange) {
    var r = document.selection.createRange();
    r.moveStart('character', -o.value.length);
    r.moveStart('character', p);
    r.moveEnd('character', 0);
    r.select();
  } else {
    o.selectionStart = p;
    o.selectionEnd = p;
  }
}

function getSelectionStart(o) {
  if (o.createTextRange) {
    var r = document.selection.createRange().duplicate();
    r.moveEnd('character', o.value.length);
    if (r.text == '') {
      return o.value.length;
    }
    return o.value.lastIndexOf(r.text);
  } else {
    return o.selectionStart;
  }
}

function getSelectionEnd(o) {
  if (o.createTextRange) {
    var r = document.selection.createRange().duplicate();
    r.moveStart('character', -o.value.length);
    return r.text.length;
  } else {
    return o.selectionEnd;
  }
}
function airlineWinOpen(doc) {
  var w = window.open(doc, 'CarrierPromo', 'scrollbars=no,resizable=no,height=' + IBE.CarrierPromoHeight + ',width=' + IBE.CarrierPromoWidth);
  if (w) w.focus();
}

function openPopupWindow(url) {
  var a = arguments;
  var h = a.length > 1 ? a[1] : 600;
  var v = a.length > 2 ? a[2] : 500;
  if (url.indexOf(':') == -1 && url.indexOf('?') == -1) url += ':popup';
  var w = window.open(url, 'PopupWindow', 'scrollbars=yes,resizable=yes,height=' + h + ',width=' + v);
  if (w) w.focus();
}

function openNewBrowserWindow(url) {
  window.open(url);
}

function goToUrl(url) {
  document.location.href = url
}

/* Simple JavaScript Inheritance
 * By John Resig http://ejohn.org/
 * MIT Licensed.
 */
// Inspired by base2 and Prototype
(function() {
  var initializing = false, fnTest = /xyz/.test(function() {
    xyz;
  }) ? /\b_super\b/ : /.*/;
  // The base Class implementation (does nothing)
  this.Class = function() {
  };

  // Create a new Class that inherits from this class
  Class.extend = function(prop) {
    var _super = this.prototype;

    // Instantiate a base class (but only create the instance,
    // don't run the init constructor)
    initializing = true;
    var prototype = new this();
    initializing = false;

    // Copy the properties over onto the new prototype
    for (var name in prop) {
      // Check if we're overwriting an existing function
      prototype[name] = typeof prop[name] == "function" &&
                        typeof _super[name] == "function" && fnTest.test(prop[name]) ?
                        (function(name, fn) {
                          return function() {
                            var tmp = this._super;

                            // Add a new ._super() method that is the same method
                            // but on the super-class
                            this._super = _super[name];

                            // The method only need to be bound temporarily, so we
                            // remove it when we're done executing
                            var ret = fn.apply(this, arguments);
                            this._super = tmp;

                            return ret;
                          };
                        })(name, prop[name]) :
                        prop[name];
    }

    // The dummy class constructor
    function Class() {
      // All construction is actually done in the init method
      if (!initializing && this.init) {
        this.init.apply(this, arguments);
      }
    }

    // Populate our constructed prototype object
    Class.prototype = prototype;

    // Enforce the constructor to be what we expect
    Class.prototype.constructor = Class;

    // And make this class extendable
    Class.extend = arguments.callee;

    return Class;
  };
})();

/*****************Globals****************/
var ckpaste_currentText = '';//initualizes the text of the text area.
var ckpaste_elem = null;//this will be the textarea.
var ckpaste_pasteMsg = 'Cannot paste into this field';
/****************************************/

/****************************************************************************
 void ckpaste_preventPaste();
 initializes the copy paste monitor.

 ****************************************************************************/

function ckpaste_preventPaste(elem, msg) {
  ckpaste_elem = elem;

  if (msg) {
    ckpaste_pasteMsg = msg
  } else {
    if (msg === false) {
      ckpaste_pasteMsg = msg;
    }
  }

  ckpaste_currentText = ckpaste_elem.defaultValue;
  if (document.all) {//it it is ie do the onpaste function
    ckpaste_elem.onpaste = ckpaste_showMessage;
    ckpaste_elem.oncontextmenu = ckpaste_showMessage;
    ckpaste_elem.onkeyup = ckpaste_checkText;
    ckpaste_elem.onblur = ckpaste_checkText;
    ckpaste_elem.onchange = ckpaste_checkText;
  } else {
    ckpaste_elem.addEventListener("keyup", ckpaste_checkText, true);
    ckpaste_elem.addEventListener("blur", ckpaste_checkText, true);
    ckpaste_elem.addEventListener("change", ckpaste_checkText, true);
  }
}

/****************************************************************************
 void ckpaste_preventPaste()
 checks the length of the string in the textbox and compares it with
 the length of the string in the saved ckpaste_currentText string. If the
 text box text is 10 characters longer than the saved string then it
 assumes that they have pasted.  it puts the current string back into
 the textarea over what they pasted and then shows them the message.
 if it isn't longer then it just puts the text into the saved text.

 ****************************************************************************/

function ckpaste_checkText(event) {
  event = event || window.event; // window.event for IE
  if (ckpaste_elem && event && event.keyCode) {
    var newTextLength = ckpaste_elem.value.length;//gets length of the textarea right now.
    var ckpaste_currentTextLength = ckpaste_currentText.length;//gets length of the saved text from the textarea
    //    if (newTextLength > (1 + ckpaste_currentTextLength)) {
    //      alert('event.keyCode: ' + event.keyCode + ', newTextLength: ' + newTextLength + ", ckpaste_currentTextLength: " + ckpaste_currentTextLength);
    //    }
    var codeForSelectingFromPrevList = 13;
    if (newTextLength > (ckpaste_currentTextLength + 10) && //is the new more then 10 characters longer?
        event.keyCode != codeForSelectingFromPrevList) {
      ckpaste_elem.value = ckpaste_currentText;//put the saved text back in/
      ckpaste_showMessage();//tell them they cannot paste.
    } else {
      ckpaste_currentText = ckpaste_elem.value;//if it is ok then save the text.
    }
  }
}
/****************************************************************************
 void ckpaste_showMessage();
 if they calling function wants to show a message when they paste then
 the message is alerted.
 ****************************************************************************/

function ckpaste_showMessage() {
  if (ckpaste_pasteMsg !== false) {
    alert(ckpaste_pasteMsg);
    return false;
  }
  return true;
}

function validEmail(f) {
  return f && f.length > 4; // Fusk
}
function submitNewsMail(f) {
  var nl = f.CRM_EMAIL;
  if (nl.defaultValue != nl.value && validEmail(nl.value)) {
    f.submit();
  } else {
    alert(UiText.get("Javascript.Util.Email"));
    nl.focus();
    nl.select();
  }
  return false;
}

function clickLink(link) {
  if (!link.nodeName) {
    link = getObj(link);
  }

  if (link.nodeName == 'a' || link.nodeName == 'A') {
    if (!link.onclick || link.onclick()) {
      var target = link.target ? link.target : '_self';
      window.open(link.href, target, '');
    }
    return false;
  }

  return true;
}

function findChildById(root, id) {
  if (nullOrUndefined(root)) {
    return document.getElementById(id);
  }
  else {
    ibeerror("Function not yet implemented.");
  }
}

function findChildrenByName(root, name) {
  if (nullOrUndefined(root)) {
    return document.getElementsByName(name);
  }
  else {
    var list = new Array();
    ibeerror("Function not yet implemented.");
  }
}

function getFieldName(elementId) {
  var d = getObjByIdThenName(elementId);
  if (d != null) return d.name;
  return '';
}

function getFieldValue(elementId, form) {
  var d = findChildById(form, elementId);
  if (d == null || d.type == 'radio') {
    var es = findChildrenByName(form, elementId);
    if (es != null) {
      if (es.length == 1) {
        var e = es[0];
        return e.value;
      } else {
        for (var i = 0; i < es.length; i++) {
          var r = es.item(i);
          if (r.checked) return r.value;
        }
      }
    }
  } else {
    if (d.type == 'checkbox') {
      return d.checked;
    } else {
      return d.value;
    }
  }
  return '';
}

function getValueFromFieldObject(field) {
  if (field == null || field.type == 'radio') {
    var es = getFieldsFromForm(field.form, field.name);
    if (es != null) {
      if (es.length == 1) {
        var e = es[0];
        return e.value;
      } else {
        for (var i = 0; i < es.length; i++) {
          var r = es.item(i);
          if (r.checked) return r.value;
        }
      }
    }
  } else {
    if (field.type == 'checkbox') {
      return field.checked;
    } else {
      return field.value;
    }
  }
  return '';
}

function stringToBool(s) {
  return s == "true";
}

function doInnerHTML(elementId, html, mode) {
  if (mode == 'append') {
    appendToInnerHTML(elementId, html);
  } else {
    setInnerHTML(elementId, html);
  }
}

function doElementsInnerHTML(element, html, mode) {
  if (mode == 'append') {
    appendToElementsInnerHTML(element, html);
  } else {
    setElementsInnerHTML(element, html);
  }
}

function setInnerHTML(elementID, html) {
  var e = getObj(elementID);
  if (e != null && propertyExists(e.innerHTML)) {
    e.innerHTML = html;
  }
}

function setElementsInnerHTML(e, html) {
  if (e != null && propertyExists(e.innerHTML)) {
    e.innerHTML = html;
  }
}

function appendToInnerHTML(elementId, html) {
  var e = getObj(elementId);
  if (e != null && propertyExists(e.innerHTML)) {
    e.innerHTML = e.innerHTML + html;
  }
}

function appendToElementsInnerHTML(e, html) {
  if (e != null && propertyExists(e.innerHTML)) {
    e.innerHTML = e.innerHTML + html;
  }
}

function getInnerHTML(elementId) {
  var e = getObj(elementId);
  if (e != null && propertyExists(e.innerHTML)) {
    return e.innerHTML;
  }
  return '';
}

function hasInnerHTML(elementId) {
  var e = getObj(elementId);
  if (e != null && propertyExists(e.innerHTML)) {
    return notEmptyString(trimString(e.innerHTML));
  }
  return false;
}

function clearInnerHTML(elementId) {
  setInnerHTML(elementId, '');
}

function notEmptyString(s) {
  return !emptyString(s);
}

function emptyString(s) {
  return s === undefined || s === null || s === "";
}

function trimString(s) {
  return s.replace(/^\s*/, "").replace(/\s*$/, "");
}

function setSrc(id, src) {
  var e = getObj(id);
  if (e != null && propertyExists(e.src)) {
    e.src = src;
  }
}

function propertyExists(property) {
  return typeof(property) !== 'undefined';
}

/**
 * Finds first parent element of type type. Searches recursively upwards the tree.
 * @param type The type to search for.
 * @param fromObj The object whos parent we examine.
 */
function findParentElementOfType(type, fromObj) {
  if (fromObj == null || fromObj == undefined) return null;
  if (fromObj.parentNode == null || fromObj.parentNode == undefined) return null;
  if (fromObj.parentNode.tagName == type) return fromObj.parentNode;
  return findParentElementOfType(type, fromObj.parentNode);
}

function removeAllTrFromTd(e) {
  do {
    var tdes = e.getElementsByTagName('td');
    if (tdes.length < 1) return;
    var tde = tdes[0];
    tde.parentNode.removeChild(tde);
  } while (tdes.length > 0)
}

/**
 * Checks if javascript function exists, returns true if that is the case.
 * @param funcName
 */
function functionExists(funcName) {
  return typeof funcName == 'string' && eval('typeof ' + funcName) == 'function';
}

function stringContainsAString(s) {
  if (s.startsWith('"') && s.endsWith('"')) return true;
  if (s.startsWith("'") && s.endsWith("'")) return true;
  return false;
}

function stringIsNumeric(s) {
  var validChars = "0123456789., ";
  return stringIncludesValidCharsOnly(s, validChars);
}

function stringIsInteger(s) {
  var validChars = "0123456789";
  return stringIncludesValidCharsOnly(s, validChars);
}

function stringIsPhoneNumber(s) {
  var validChars = "0123456789+-() ";
  return stringIncludesValidCharsOnly(s, validChars);
}

function stringIsDigitsAndDash(s) {
  var validChars = "0123456789-";
  return stringIncludesValidCharsOnly(s, validChars);
}

function stringIsPersonsName(s) {
  var validChars = "abcdefghijklmnopqrstuvwxyzåäöABCDEFGHIJKLMNOPQRSTUVWXYZÅÄÖ- ";
  return stringIncludesValidCharsOnly(s, validChars);
}

/**
 * Does not allow -
 * @param s
 */
function stringIsPersonsNameAirlineFormat(s) {
  var validChars = "abcdefghijklmnopqrstuvwxyzåäöABCDEFGHIJKLMNOPQRSTUVWXYZÅÄÖ ";
  return stringIncludesValidCharsOnly(s, validChars);
}

function stringIncludesValidCharsOnly(s, validChars) {
  if (s === undefined || s === null) return true;
  for (var i = 0; i < s.length; i++) {
    var c = s.charAt(i);
    if (validChars.indexOf(c) == -1) return false;
  }
  return true;
}

function stringExcludesInvalidChars(s, invalidChars) {
  if (s === undefined || s === null) return true;
  for (var i = 0; i < s.length; i++) {
    var c = s.charAt(i);
    if (invalidChars.indexOf(c) >= 0) return false;
  }
  return true;
}

function findItemWithId(list, id) {
  if (list && list.length) {
    for (var i = 0; i < list.length; i++) {
      if (list[i] && list[i].id == id) return list[i];
    }
  }
  return undefined;
}

function findItemWithPropertyEquals(list, propertyName, propertyValue) {
  if (list && list.length) {
    for (var i = 0; i < list.length; i++) {
      if (list[i] && list[i][propertyName] == propertyValue) return list[i];
    }
  }
  return undefined;
}

function findItemsWithIdStartingWith(list, idPrefix) {
  idPrefix = idPrefix.toString();
  var outList = [];
  if (list && list.length) {
    for (var i = 0; i < list.length; i++) {
      if (("" + list[i] && list[i].id).toString().startsWith(idPrefix)) outList.push(list[i]);
    }
  }
  return outList;
}

function hasItemWithId(list, id) {
  return findItemWithId(list, id) ? true : false;
}

function removeItemWithId(list, id) {
  if (list && list.length) {
    for (var i = 0; i < list.length; i++) {
      if (list[i] && list[i].id == id) {
        list.splice(i, 1);
      }
    }
  }
}

function removeItem(list, item) {
  if (list && list.length) {
    for (var i = 0; i < list.length; i++) {
      if (equalsObject(list[i], item)) {
        list.splice(i, 1);
      }
    }
  }
}

function ibedebug(s) {
  ibelog(s);
}

/**
 * Logs a string, if id is set, appends it to the element with that id. If not it checks if console.log is available
 * and uses that. If not, see if element with id 'console' is available. If not, alert is used.
 * @param s The string to be logged.
 * @param id The id of the element.
 */
function ibelog(s, id) {
  if (isProdEnvironment()) return;
  var e = getObj('console');
  if (getObj(id) != null) {
    appendToInnerHTML(id, s + '<br>\n');
  }
  else {
    if (typeof console == 'object' && typeof console.log == 'function') {
      console.log(s);
    }
    else {
      if (e != null) {
        appendToInnerHTML('console', s + '<br>\n');
      } else {
        if (isDevEnvironment()) {
          return;
          if (__logAlertCounter < __logAlertLimit) alert(s);
          __logAlertCounter++;

          if (__logAlertCounter == __logAlertLimit) {
            alert('Alert limit has been reached, no more alerts will be shown. Limit=' + __logAlertLimit);
          }
        }
      }
    }
  } // Do not allow alert in production environment.
}

/**
 * Singleton instance that manages JS-unit testing.
 */
var IBETests = (function() {
  var noticesAreLoggedStack = [true];
  var warningThrowsExceptionStack = [false];
  var errorThrowsExceptionStack = [false]; // Contains booleans, topmost value is current value

  var eventStartPrefix = "testModuleStart";
  var eventEndPrefix = "testModuleEnd";

  var debugMode = false;

  // Private function
  function runIbeTests(args) {

  }

  // public interface methods
  return {
    getNoticesAreLogged : function() {
      if (!noticesAreLoggedStack.length) return false;
      return noticesAreLoggedStack.peek();
    },
    getWarningThrowsException : function() {
      if (!warningThrowsExceptionStack.length) return false;
      return warningThrowsExceptionStack.peek();
    },
    getErrorThrowsException : function() {
      if (!errorThrowsExceptionStack.length) return false;
      return errorThrowsExceptionStack.peek();
    },

    pushNoticesAreLogged : function(v) {
      noticesAreLoggedStack.push(v ? true : false);
    },
    pushWarningThrowsException : function(v) {
      warningThrowsExceptionStack.push(v ? true : false);
    },
    pushErrorThrowsException : function(v) {
      errorThrowsExceptionStack.push(v ? true : false);
    },

    popNoticesAreLogged : function() {
      noticesAreLoggedStack.pop();
      if (noticesAreLoggedStack.length == 0) noticesAreLoggedStack.push(false); // Ensure has value
    },
    popWarningThrowsException : function() {
      warningThrowsExceptionStack.pop();
      if (warningThrowsExceptionStack.length == 0) warningThrowsExceptionStack.push(false); // Ensure has value
    },
    popErrorThrowsException : function() {
      errorThrowsExceptionStack.pop();
      if (errorThrowsExceptionStack.length == 0) errorThrowsExceptionStack.push(false); // Ensure has value
    },

    /**
     * @param args is an object containing properties to be used when the test are run.
     * @see runIbeTest(args) function for the default properties.
     */
    runTests : function(args) {
      runIbeTests(args);
    },
    init : function(args) {
      QUnit.moduleStart = function(args) {
        var eventId = formatCustomEventName(eventStartPrefix);
        $.event.trigger(eventId, {});
        eventId = formatCustomEventName(eventStartPrefix + args.name);
        $.event.trigger(eventId, {});
      };
      QUnit.moduleDone = function(args) {
        var eventId = formatCustomEventName(eventEndPrefix);
        $.event.trigger(eventId, {});
        eventId = formatCustomEventName(eventEndPrefix + args.name);
        $.event.trigger(eventId, {});
      }
    },

    /**
     * Set current test module, for example "ibe-component" or "charter". You can set callbacks for before and after done with that module.
     * You can use this to enable exceptions for warning and errors for example, which can be detected by test framework.
     *
     * @param moduleName
     * @param beforeCallback
     * @param afterCallback
     */
    setTestModule : function(moduleName, beforeCallback, afterCallback) {
      if (debugMode) ibelogs("setTestModule", moduleName);
      var eventId = formatCustomEventName(eventStartPrefix + moduleName);
      if (typeof beforeCallback === "function") {
        $('body').bind(eventId, {name: moduleName }, function() {
          if (debugMode) ibelogs("beforeModule()", moduleName);
          beforeCallback({ name:name });
        });
      } else {
        ibeerror("Trying to set module start callback for QUnit testing, but callback is not a function. Module name=" + moduleName);
      }
      eventId = formatCustomEventName(eventEndPrefix + moduleName);
      if (typeof afterCallback === "function") {
        $('body').bind(eventId, {name: moduleName }, function() {
          if (debugMode) ibelogs("afterModule()", moduleName);
          afterCallback({ name:name });
        });
      } else {
        ibeerror("Trying to set module end callback for QUnit testing, but callback is not a function. Module name=" + moduleName);
      }
      module(moduleName);
    }
  };

})();

/**
 * Formats a string so that it can be used as custom event name in jQuery.
 * @param s
 */
function formatCustomEventName(s) {
  s = s.replace(/ /g, ''); // Remove all spaces, not allowed.
  s = s.replace(/\./g, ''); // Remove all spaces, not allowed.
  s = s.replace(/\-/g, ''); // Remove all spaces, not allowed.
  return s;
}

function stripNonNumeric(str) {
  str += '';
  var rgx = /^\d|\.|-$/;
  var out = '';
  for (var i = 0; i < str.length; i++) {
    if (rgx.test(str.charAt(i))) {
      if (!( ( str.charAt(i) == '.' && out.indexOf('.') != -1 ) ||
             ( str.charAt(i) == '-' && out.length != 0 ) )) {
        out += str.charAt(i);
      }
    }
  }
  return out;
}

function ibelogs() {
  if (isProdEnvironment()) return;
  if (
    (typeof console == 'object' && typeof console.log == 'function') ||
    (typeof console == 'object' && typeof console.log == 'object' && browserIsIE9()) // IE9 console.log is of type "object"
    ) {
    var list = [];
    $.each(arguments, function(i, arg) {
      // Make a copy of the array/object, so that if the original object is updated, the log is not.
      if ($.isArray(arg)) {
        list.push(arg.clone());
      } else {
        if (typeof arg == "object") {
          var o = {};
          copyObject(arg, o);
          list.push(o);
        } else {
          list.push(arg);
        }
      }
    });
    console.log(list);
  }
}

function ibeloga(a) {
  if (a && a.length) {
    for (var i = 0; i < a.length; i++) {
      ibelogs(i, + a[i]);
    }
  }
}

/**
 *
 * @param s
 * @param s2
 */
function ibenotice(s, s2) {
  if (isProdEnvironment()) return;
  if (IBETests.getNoticesAreLogged()) {
    if (s2) {
      ibelogs(s, s2);
    } else {
      ibelogs(s);
    }
  }
}

/**
 * Logs a warning message to console. Should not output anything if in production!
 * @param s The string to be logged.
 */
function ibewarning(s, s2) {
  if (isProdEnvironment()) return;
  if (IBETests.getWarningThrowsException()) {
    ibethrow(s);
  } else if (typeof console == 'object' && typeof console.warning == 'function') {
    console.warning(s);
  } else {
    if (s2) {
      ibelogs("Warning!", s, s2);
    } else {
      ibelogs("Warning!", s);
    }
  }
}

/**
 * Logs an error message to console. Should not output anything if in production!
 * @param s The string to be logged.
 */
function ibeerror(s, s2) {
  if (isProdEnvironment()) return;
  if (IBETests.getErrorThrowsException()) {
    ibethrow(s);
  } else if (typeof console == 'object' && typeof console.error == 'function') {
    console.error(s);
    ibetrace();
  } else {
    alert("Error: " + s);
  }
}

function ibetrace() {
  if (typeof console == 'object' && typeof console.trace == 'function') {
    console.trace();
  }
}

function ibethrow(m) {
  if (isProdEnvironment()) return;
  throw m;
}

/**
 * Checks to see if we are in debug environment, that is, NOT in production environment.
 */
function isDebugEnvironment() {
  return IBE.debug;
}

/**
 * Checks to see if we are in debug environment, that is, NOT in production environment.
 */
function isProdEnvironment() {
  return IBE.environment === 'prod';
}

function isTestEnvironment() {
  return IBE.environment === 'test';
}

function isDevEnvironment() {
  return IBE.environment === 'dev';
}

function nlToBr(s) {
  var tmp = undefined;
  var limit = 1000;
  while (limit > 0) {
    tmp = s.replace('\n', '<br>');
    if (tmp == s) break;
    s = tmp;
    limit--;
  }
  return tmp;
}

function spaceToNbsp(s) {
  var tmp = undefined;
  var limit = 1000;
  while (limit > 0) {
    tmp = s.replace(' ', '&nbsp;');
    if (tmp == s) break;
    s = tmp;
    limit--;
  }
  return tmp;
}


/**
 * Prints all properties for an object.
 * @param o
 */
function printAll(o) {
  ibelog(printAllToString(o));
}

function printArray(a) {
  ibelog('Array[' + a.length + '] =');
  for (var i = 0; i < a.length; i++) {
    var v = a[i];
    ibelog('  [' + i + '] =>');
    ibelog(v);
  }
}

function printAllToString(o) {
  var r = new String();
  r += '-- All variables in Object --\n';
  for (var prop in o) {
    if (typeof prop !== 'string') r += prop + ' (' + typeof prop + ') =' + o[prop] + '\n';
  }
  r += '----\n';
  return r;
}

function getObjectsProperties(o) {
  var l = [];
  var i = 0;
  for (var p in o) {
    l[i++] = p;
  }
  return l;
}

function enableId(id) {
  var e = getObj(id);
  if (e) e.disabled = false;
}

function disableId(id) {
  var e = getObj(id);
  if (e) e.disabled = true;
}

function autoCompleteOff(f) {
  f.setAttribute("autocomplete", "off");
}

function setElementsClass(id, className) {
  var e = getObj(id);
  if (e != null && propertyExists(e.className)) {
    e.className = className;
  }
}

function executeFunctionList(list) {
  if (list && list.length) {
    var i, s = list.length;
    for (i = 0; i < s; i++) {
      var f = list[i];
      if (typeof f === "function") {
        setTimeout(f, 0);
      } else {
        ibewarning("Trying to execute list of functions, but item i=" + i + " is not a function.");
      }
    }
  }
}

function disableSelection(element) {
  if (element) {
    if (typeof element.onselectstart != "undefined") {
      //IE
      element.onselectstart = function() {
        return false;
      };
    } else {
      if (typeof element.style.MozUserSelect != "undefined") {
        //Firefox
        element.style.MozUserSelect = "none";
      } else {
        //Others
        element.onmousedown = function() {
          return false;
        };
      }
    }
  }
}

function useDefault(value, defaultValue) {
  if (value === undefined || value === null) {
    return defaultValue;
  } else {
    return value;
  }
}

function trim(str) {
  return str.replace(/^\s*|\s*$/g, "");
}

function cssClassFriendly(str) {
  str += '';
  return str.replace(/\.|\,/g, '_');
}

function implode(list, separator) {
  var r = '';
  var isFirst = true;
  for (var i = 0; i < list.length; i++) {
    if (isFirst === true || separator === undefined) {
      isFirst = false;
    } else {
      r = r + separator;
    }
    r = r + list[i];
  }
  return r;
}

function removeListHead(list) {
  var nl = new Array();
  for (var i = 1; i < list.length; i++) {
    nl[i - 1] = list[i];
  }
  return nl;
}

/**
 * Same as split, but for lists. For example.
 * 0: "mattias is"
 * 1: "awesome"
 * separator: " "
 * will result in
 * 0: "mattias"
 * 1: "is"
 * 2: "awesome"
 * @param list
 * @param separator
 */
function splitList(list, separator) {
  var nlist = new Array();
  for (var i = 0; i < list.length; i++) {
    var l = list[i].split(separator);
    for (var j = 0; j < l.length; j++) {
      nlist[nlist.length] = l[j];
    }
  }
  return nlist;
}

String.prototype.splitTwice = function(separator1, separator2) {
  var splitLeft = this.split(separator1);
  var stack = new Array();
  return splitList(splitLeft, separator2);
};

String.prototype.format = function() {
  var s = this;
  for (var i = 0; i < arguments.length; i++) {
    var reg = new RegExp("\\{" + i + "\\}", "gm");
    s = s.replace(reg, arguments[i]);
  }
  return s;
};


function startsWith(str, v) {
  return str.substring(0, v.length) == v;
}

function endsWith(str, v) {
  return str.substring(v.length - 1, v.length) == v;
}

String.prototype.trim = function() {
  return (this.replace(/^[\s\xA0]+/, "").replace(/[\s\xA0]+$/, ""));
};

String.prototype.startsWith = function(str) {
  return (this.match("^" + str) == str);
};

String.prototype.endsWith = function(str) {
  return (this.match(str + "$") == str);
};

String.prototype.getLastCharacter = function() {
  if (this.length > 0) {
    return this.charAt(this.length - 1);
  } else {
    ibeerror("String.lastCharacter, but string is empty.");
    return undefined;
  }
};

function subList(list, startIndex, length) {
  var rlist = new Array();
  var endIndex = startIndex + length;
  if (length < 0) endIndex = list.length - 1;
  for (var i = startIndex; i < endIndex; i++) {
    rlist[rlist.length] = list[i];
  }

  return rlist;
}

function nullOrUndefined(s) {
  return s === null || s === undefined;
}

function nonFalse(s) {
  return s === undefined || s;
}

function nonTrue(s) {
  return !(s === undefined || !s);
}

Array.prototype.inArray = function (value) {
  for (var i = 0; i < this.length; i++) {
    if (this[i] == value) {
      return true;
    }
  }
  return false;
};

Array.prototype.contains = function (value) {
  for (var i = 0; i < this.length; i++) {
    if (this[i] == value) {
      return true;
    }
  }
  return false;
};

Array.prototype.containsById = function (value) {
  validateArgument(value, {rules:{allowUndefined:false, requireObject:true}}, "value");
  var hasUndefined = false;
  var undefinedIndex = -1;
  var result = false;
  for (var i = 0; i < this.length; i++) {
    if (!this[i]) {
      hasUndefined = true;
      if (undefinedIndex < 0) undefinedIndex = i;
    } else if (this[i].id == value.id) {
      result = true;
      break;
    }
  }
  if (hasUndefined) {
    ibewarning("Array.containsById(..) found element in self that is undefined. index=" + undefinedIndex, this);
  }
  return result;
};

Array.prototype.containsOnlyNumbers = function (value) {
  return this.containsOnly(value, "number");
};

Array.prototype.containsOnlyStrings = function (value) {
  return this.containsOnly(value, "string");
};

Array.prototype.containsOnly = function (value, type) {
  for (var i = 0; i < this.length; i++) {
    if (typeof this[i] !== type) {
      return false;
    }
  }
  return true;
};

Array.prototype.peek = function () {
  if (this.length === 0) return undefined;
  return this[this.length - 1];
};

Array.prototype.pushAll = function (list) {
  try {
    var that = this;
    $.each(list, function(key, obj) {
      that.push(obj);
    });
  } catch (e) {
    ibeerror("Array.pushAll failed: " + e);
  }
};

Array.prototype.remove = function (value) {
  for (var i = 0; i < this.length; i++) {
    if (this[i] == value) {
      this.splice(i, 1);
    }
  }
};

Array.prototype.indexInArray = function (value) {
  for (var i = 0; i < this.length; i++) {
    if (this[i] == value) {
      return i;
    }
  }
  return -1;
};

function keyCount(obj) {
  var i = 0;
  for (key in obj) {
    i++;
  }
  return i;
}


Array.prototype.equalsArray = function(b) {
  return equalsObject(this, b);
  /*
   if (this == b) {
   return true;
   }
   if (this.keyCount() != b.keyCount()) {
   return false;
   }
   for (key in this) {
   if (this[key] != b[key]) {
   return false;
   }
   }
   return true;
   */
};

Array.prototype.equals = function(a) {
  if (this == a) return true;
  if (this.length != a.length) return false;
  for (var i = 0; i < this.length; i++) {
    var b = equalsObject(a[i], this[i]);
    if (!b) return false;
  }
  return true;
};

Array.prototype.clone = function() {
  var a = new Array();
  for (var i = 0; i < this.length; i++) {
    a[i] = this[i];
  }
  return a;
};

function equalsObject(a, b) {
  if (a == b) {
    return true;
  }
  if (keyCount(a) != keyCount(b)) {
    return false;
  }
  for (key in a) {
    if (key === "__proto__") {
      //ibelogs("Skipping __proto__");
      continue;
    }
    var v1 = a[key];
    var v2 = b[key];
    if (typeof v1 === "function") {
      //ibelogs("Skipping function");
      continue;
    }
    if (typeof v1 === "number" || typeof v1 === "string") {
      if (v1 != v2) {
        //ibelogs("equalsObject FALSE!", v1, v2, a, b);
        return false;
      }
    } else {
      if (!equalsObject(v1, v2)) return false;
    }
  }
  return true;
}

function getCookie(name) {
  //console.log('getCookie name=' + name);
  if (!name) return null;
  var start = document.cookie.indexOf(name + "=");
  var len = start + name.length + 1;
  if (( !start ) && ( name != document.cookie.substring(0, name.length) )) {
    return null;
  }
  if (start == -1) return null;
  var end = document.cookie.indexOf(';', len);
  if (end == -1) end = document.cookie.length;
  var r = unescape(document.cookie.substring(len, end));
  //console.log('=' + r);
  return r;
}

function setCookie(name, value, expires, path, domain, secure) {
  //console.log('setcookie name=' + name + ' value=' + value);
  if (!name) return;
  var today = new Date();
  today.setTime(today.getTime());
  if (expires) {
    expires = expires * 1000 * 60 * 60 * 24;
  }
  var expires_date = new Date(today.getTime() + (expires));
  document.cookie = name + '=' + escape(value) +
                    ( ( expires ) ? ';expires=' + expires_date.toGMTString() : '' ) + //expires.toGMTString()
                    ( ( path ) ? ';path=' + path : '' ) +
                    ( ( domain ) ? ';domain=' + domain : '' ) +
                    ( ( secure ) ? ';secure' : '' );
}

function deleteCookie(name, path, domain) {
  if (getCookie(name)) {
    document.cookie = name + '=' +
                      ( ( path ) ? ';path=' + path : '') +
                      ( ( domain ) ? ';domain=' + domain : '' ) +
                      ';expires=Thu, 01-Jan-1970 00:00:01 GMT';
  }
}

function getElementsByClass(searchClass, node, tag) {
  var classElements = new Array();
  if (node == null) {
    node = document;
  }
  if (tag == null) {
    tag = '*';
  }
  var els = node.getElementsByTagName(tag);
  var elsLen = els.length;
  var pattern = new RegExp('(^|\\\\s)' + searchClass + '(\\\\s|$)');
  var j = 0;
  for (var i = 0; i < elsLen; i++) {
    if (pattern.test(els[i].className)) {
      classElements[j] = els[i];
      j++;
    }
  }
  return classElements;
}

function removeFnutts(s) {
  if (s.length > 2) {
    if ((s.startsWith('\'') && s.endsWith('\'')) ||
        (s.startsWith('"') && s.endsWith('"')    )) {
      return s.substring(1, s.length - 1);
    }
  }
  return s;
}

function isArray(obj) {
  if (nullOrUndefined(obj)) return false;
  return $.isArray(obj);
}

function addSelectOption(selectbox, text, value, cssClass, selected) {
  if (selectbox) {
    var e = document.createElement("OPTION");
    e.text = text;
    e.value = value;
    if (cssClass) e.className = cssClass;
    if (selected) {
      e.selected = true;
    }
    selectbox.options.add(e);
  }
}

function addSelectOptionObject(selectbox, option) {
  if (selectbox) {
    selectbox.options.add(option);
  }
}

function findOptionByValue(selectbox, value) {
  for (i = selectbox.options.length - 1; i >= 0; i--) {
    if (selectbox.options[i].value == value) return selectbox.options[i];
  }
  return null;
}

function selectOption(selectbox, value) {
  for (i = selectbox.options.length - 1; i >= 0; i--) {
    if (selectbox.options[i].value == value) {
      selectbox.selectedIndex = i;
      return;
    }
  }
}

function clearSelectOptions(selectbox) {
  for (var i = selectbox.options.length - 1; i >= 0; i--) {
    // Must be done backwards, otherwise it doesn't work.
    selectbox.options[i] = null;
  }
}

function greatCircleDistance(lat1, lon1, lat2, lon2) {
  var R = 6371; // km
  var d = Math.acos(Math.sin(lat1) * Math.sin(lat2) +
                    Math.cos(lat1) * Math.cos(lat2) *
                    Math.cos(lon2 - lon1)) * R;
  return d;
}

function greatCircleDistance2(lat1, lon1, lat2, lon2) {
  var R = 6371; // km
  var dLat = (lat2 - lat1).toRad();
  var dLon = (lon2 - lon1).toRad();
  var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
          Math.cos(lat1.toRad()) * Math.cos(lat2.toRad()) *
          Math.sin(dLon / 2) * Math.sin(dLon / 2);
  var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  var d = R * c;
  return d;
}

function distanceBetweenTwoPoints(x1, y1, x2, y2) {
  var dx = Math.abs(x2 - x1);
  var dy = Math.abs(y2 - y1);
  return Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
}

// extend Number object with methods for converting degrees/radians

Number.prototype.toRad = function() {  // convert degrees to radians
  return this * Math.PI / 180;
};

Number.prototype.toDeg = function() {  // convert radians to degrees (signed)
  return this * 180 / Math.PI;
};

Number.prototype.toBrng = function() {  // convert radians to degrees (as bearing: 0...360)
  return (this.toDeg() + 360) % 360;
};

function setInnerHtmlToLoadingAnimation(elementID, className) {
  className = className || "largeLoadingAnimationContainer";
  setInnerHTML(elementID, '<div class="' + className + '">' +
                          '<img src="/system/image/loading_animation_small.gif" alt="Loading"/>' +
                          '</div>');

}

function setInnerHtmlToSmallLoadingAnimation(elementID, width, paddingTop) {
  if (paddingTop === undefined) paddingTop = "2px";
  setInnerHTML(elementID, '<img style="padding-left:8px; padding-top: ' + paddingTop + ';' +
                          (width ? 'width:' + width + ';' : '') +
                          '" src="/system/image/loading_16x16.gif" alt="Loading"/>');

}

function getRandomUnusedId(id) {
  while (getObj(id)) {
    id = id + (Math.random() * 10);
  }
  return id;
}

function clearElement(element) {
  if (element) while (element.firstChild) element.removeChild(element.firstChild);
}

/** Context class **/
function Context(key) {

  this.contextKey = key;

  this.get = function(key) {
    if (key) {
      if (this.contextKey) {
        return YAHOO.util.Cookie.getSub(this.contextKey, key);
      } else {
        return YAHOO.util.Cookie.get(key);
      }
    }
  };

  this.set = function(key, obj) {
    if (key) {
      if (this.contextKey) {
        YAHOO.util.Cookie.setSub(this.contextKey, key, obj);
      } else {
        YAHOO.util.Cookie.set(key, obj);
      }
    }
  };

  this.remove = function(key) {
    if (key) {
      if (this.contextKey) {
        YAHOO.util.Cookie.removeSub(this.contextKey, key);
      } else {
        YAHOO.util.Cookie.remove(key);
      }
    }
  };
  /**
   * Delete all for this context or the context specified by "key"
   * @param key
   */
  this.removeAll = function(key) {
    if (this.contextKey) {
      YAHOO.util.Cookie.remove(this.contextKey);
    } else {
      YAHOO.util.Cookie.remove(key);
    }
  };
}


function logoutCustomer() {
  // TODO: Fix ugly hack
  AjaxLogoutUser.startRequest(function(result) {
    if (YAHOO.util.Dom.hasClass(document.body, 'user-profiles')) {
      redirect("");
    } else {
      reloadPage();
    }
  });
}

function getSmallerAnimationHTML() {
  return '<img alt="Loading..." src="/system/image/loading_animation_smaller.gif"/>';
}

function getSmallestAnimationHTML() {
  return '<img alt="Loading..." src="/system/image/loading16x16.gif"/>';
}

/**
 * If used within an iframe, only the iframe will reload!
 */
function reloadPage() {
  window.location.reload();
}

/**
 * Reloads the window, this can be used even if in an iframe. Iframe will not reload, but instead the whole page.
 */
function reloadWindow() {
  top.location.reload();
}

function reloadParent() {
  if (parent) {
    parent.location.reload();
  }
}

function redirectOnTimeout(action, timeOut) {
  timeOut = useDefault(timeOut, 5000);
  setTimeout(function () {
    redirect(action);
  }, timeOut);
}

function redirect(action) {
  redirectPure(checkAction(action));
}

function redirectPure(path) {
  window.location.pathname = path;
}

/**
 * Shows an alert if the message is defined and not empty string, if the condition is undefined or true.
 * @param s
 * @param condition
 */
function ibealert(s, condition) {
  if (condition === undefined || condition === true) {
    if (s) alert(s);
  }
}

function checkAction(url) {
  if (emptyString(url)) return "";
  url = prependIfNotThere(url, "/");
  if (url != "/" && url.indexOf("?") == -1) {
    url = appendIfNotThere(url, ".action");
  }
  return url;
}

function appendIfNotThere(base, add) {
  return (base.endsWith(add) ? base : base + add);
}

function prependIfNotThere(base, add) {
  return (base.startsWith(add) ? base : add + base);
}

/**
 * Returns all fields in a form with a given name. Usually just one, but radio buttons usually have more than one.
 * @param form
 * @param fieldName
 */
function getFieldsFromForm(form, fieldName) {
  var result = new Array();
  if (!form) {
    ibeerror("Trying to get field in form that is undefined.");
    return undefined;
  }
  var elements = form.elements;
  if (elements) {
    for (var i = 0; i < elements.length; i++) {
      var element = elements[i];
      if (element.name == fieldName) {
        result.push(element);
      }
    }
  }
  if (result.length == 0) return null;
  return result;
}

function getFieldFromForm(form, fieldName) {
  var r = getFieldsFromForm(form, fieldName);
  if (r) {
    return r[0];
  }
  else {
    return null;
  }
}

function getSelectFieldValue(field) {
  var options = field.options;
  for (var i = 0; i < options.length; i++) {
    var option = options[i];
    if (option.selected) return option.value;
  }
  return null;
}

function getSelectedOption(selectField) {
  var options = selectField.options;
  if (options && options.length) {
    for (var i = 0; i < options.length; i++) {
      var option = options[i];
      if (option.selected) return option;
    }
  }
  return null;
}

function getIndexOfOptionWithInnerHtml(selectField, html) {
  var options = selectField.options;
  if (options && options.length) {
    for (var i = 0; i < options.length; i++) {
      var option = options[i];
      if (option.innerHTML === html) return i;
    }
  }
  return -1;
}

function getValueOfOptionWithInnerHtml(selectField, html) {
  var options = selectField.options;
  if (options && options.length) {
    for (var i = 0; i < options.length; i++) {
      var option = options[i];
      if (option.innerHTML === html) return option.value;
    }
  }
  return null;
}

//See ValidationUtil.java for description and ValidationUtilTest.java for tests
function isValidPassword(password) {
  var regex = /^[^\s]{6,}$/;
  return regex.test(password);
}

function isValidEmail(email) {
  var regex = /^\w+([\.\-\+]?\w+)*@\w+([\.\-\+]?\w+)*(\.\w{2,})+$/;
  return regex.test(email);
}

/* Matches 6 to 15 digits, used for phone numbers, see standard E.164 */
function isValidPhoneNumber(phoneNumber) {
  var regex = /^\d{6,15}$/;
  return regex.test(trimPhoneNumber(phoneNumber));
}

/* Trims away " .-" and leading "00" or "+" */
function trimPhoneNumber(phoneNumber) {
  var regex = /^00|^\+|[\s.-]+/;
  return phoneNumber.replace(regex, "");
}

function parseJsonList(s) {
  var e = eval(s);
  return e;
}

function parseJsonObject(s) {
  var e = eval('[' + s + ']');
  return e[0];
}

function isEmptyArray(o) {
  if (o === undefined) return true;
  if (o === null) return true;
  if (o.length == 0) {
    return true;
  } else {
    return false;
  }
}

function isNotEmptyArray(array) {
  return !isEmptyArray(array);
}

function deleteElementInArray(array, index) {
  array.splice(index, 1);
}

function runSoon(func, delayInMs) {
  setTimeout(func, delayInMs);
}

/**
 * @param url The URL (as string) that contains URL parameters.
 *
 * @return an object representing the URL parameters. If URL is not defined, returns empty object.
 */
function deserializeUrlString(url) {
  if (!url) return {};
  var values = url.split("&");
  var o = {};
  for (var i = 0; i < values.length; i++) {
    var v = values[i].split("=");
    o[v[0]] = decodeURIComponent(v[1]);
  }
  return o;
}

/****************
 Fading functions
 ****************/

function fade(elementId, timeToFade) {
  if (!timeToFade) timeToFade = 1000.0;

  var element = document.getElementById(elementId);
  if (element == null) return;
  element.FadeState = null;

  if (element.FadeState == null) {
    if (element.style.opacity == null || element.style.opacity == '' || element.style.opacity == '1') {
      element.FadeState = 2;
    } else {
      element.FadeState = -2;
    }
  }

  if (element.FadeState == 1 || element.FadeState == -1) {
    element.FadeState = element.FadeState == 1 ? -1 : 1;
    element.FadeTimeLeft = timeToFade - element.FadeTimeLeft;
  } else {
    element.FadeState = element.FadeState == 2 ? -1 : 1;
    element.FadeTimeLeft = timeToFade;
    setTimeout("animateFade(" + new Date().getTime() + ",'" + elementId + "', " + timeToFade + ")", 33);
  }
}

function animateFade(lastTick, elementId, timeToFade) {
  if (!timeToFade) timeToFade = 1000.0;
  var curTick = new Date().getTime();
  var elapsedTicks = curTick - lastTick;

  var element = document.getElementById(elementId);

  if (element.FadeTimeLeft <= elapsedTicks) {
    element.style.opacity = element.FadeState == 1 ? '1' : '0';
    element.style.filter = 'alpha(opacity = '
                             + (element.FadeState == 1 ? '100' : '0') + ')';
    element.FadeState = element.FadeState == 1 ? 2 : -2;
    return;
  }

  element.FadeTimeLeft -= elapsedTicks;
  var newOpVal = element.FadeTimeLeft / timeToFade;
  if (element.FadeState == 1)    newOpVal = 1 - newOpVal;

  element.style.opacity = newOpVal;
  element.style.filter = 'alpha(opacity = ' + (newOpVal * 100) + ')';


  setTimeout("animateFade(" + curTick + ",'" + elementId + "')", 33);
}

function insertArgument(text, arg0, arg1, arg2, arg3) {
  if (arg0 != null) text = text.split('{0}').join(arg0);
  if (arg1 != null) text = text.split('{1}').join(arg1);
  if (arg2 != null) text = text.split('{2}').join(arg2);
  if (arg3 != null) text = text.split('{3}').join(arg3);
  return text;
}

function setZIndex(elementId, zIndex) {
  var e = getObj(elementId);
  if (e) e.style.zIndex = zIndex;
}

function exceptionToString(e) {
  if (e.description) {
    return e.description;
  }
  else {
    if (e.toString) {
      return e.toString();
    }
    else {
      return e;
    }
  }
}

function elementExists(elementId) {
  return getObj(elementId) ? true : false;
}

function enableOptionDisabledInIE(select) {
  window.select_current = [];
  $(select).focus(function() {
    window.select_current[this.id] = this.selectedIndex;
  });
  /*
   select.onfocus = function() {
   window.select_current[this.id] = this.selectedIndex;
   };
   */
  $(select).click(function() {
    restoreOptionDisable(this);
  });
  /*select.onchange = function() {
   restoreOptionDisable(this);
   };*/
  emulateOptionDisable(select);
}

function restoreOptionDisable(e) {
  if (e.options[e.selectedIndex].disabled) {
    e.selectedIndex = window.select_current[e.id];
  }
}

function emulateOptionDisable(e) {
  for (var i = 0, option; option = e.options[i]; i++) {
    var color;
    if (option.disabled) {
      color = "graytext";
    } else {
      color = "menutext";
    }
    option.style.color = color;
  }
}

function dateStringToDateObject(dateString) {
  if (!dateString) {
    return null;
  } else {
    var arr = dateString.split("-");
    return dateArrayToDateObject(arr);
  }
}

function dateArrayToDateObject(dateArray) {
  return new Date(dateArray[0], dateArray[1] - 1, dateArray[2]);
}

function _isDecSepa(c) {
  return c == '.' || c == ',';
}

function _decCharIdxOf(s, c) {
  var i = s.indexOf(c);
  if (i == -1) {
    if (c == ',') {
      i = s.indexOf('.');
    } else {
      if (c == '.') i = s.indexOf(',');
    }
  }
  return i;
}

//
// Formats the number according to the following examples:
//
// 123434        ->    123 434
// 12123123434   ->    12 123 123 434
// 123           ->    123
// 123.123       ->    123,12
// 1123.123      ->    1 123,12
//
// @param n             The number to format
// @param decimals      (optional) The number of decimals to keep
// @param decimapChar   (optional) The character to use as separator
// @param formatString  (optional) A String to format using the formated number as first arg.
//                      May be a loaded UI-text key.
// @param prefixPositiveValueWithPlusChar  (optional) When you want value a positive value to be prefixed with a '+' char like negative values have a '-' char.
// Examples:
//
//  fNum(1234.123)                           : '1 234.12'
//  fNum(1234.123,1)                         : '1 234.1'
//  fNum(1234.123,1,',')                     : '1 234,1'
//  fNum(1234.123,1,',', '€ {0}')            : '€ 1 234,1'
//  fNum(1234.123,0,',', '€ {0}')            : '€ 1 234'
//  fNum(1234.123,0,',', '€ {0}', true)      : '€ +1 234'
//
//
function fNum(n) {
  var d = 2;
  var decimalChar = '.';
  var format = undefined;
  var prefixPositiveValueWithPlusChar = undefined;
  var a = arguments;
  if (a.length > 1) d = a[1];
  if (a.length > 2) decimalChar = a[2];
  if (a.length > 3) format = a[3];
  if (a.length > 4) prefixPositiveValueWithPlusChar = a[4];
  if (d > 0) d += 1;
  if (n == undefined) return '';
  if (d == 0) n = Math.round(n);
  var vs = '' + n;
  var i = _decCharIdxOf(vs, decimalChar);
  if (i >= 0 && vs.length > (i + d)) {
    n = new Number(vs.substring(0, (i + d))).valueOf();
    vs = '' + n;
  } else {
    if (d > 0) {
      var zerosMissing;
      if (i < 0) {
        vs = vs + decimalChar;
        zerosMissing = d - 1;
      } else {
        zerosMissing = (i + d) - vs.length;
      }
      for (var z = 0; z < zerosMissing; z++) {
        vs = vs + '0';
      }
    }
  }
  if (vs.length - d > 3) {
    i = _decCharIdxOf(vs, decimalChar);
    var b = 0;
    var tmp = (i > 0 ? vs.substring(i, vs.length) : '');
    for (var y = (vs.length - tmp.length - 1); y >= 0; y--) {
      b++;
      tmp = vs.substring(y, y + 1) + tmp;
      if (b == 3) {
        tmp = ' ' + tmp;
        b = 0;
      }
    }
    vs = tmp;
  }

  if (prefixPositiveValueWithPlusChar && n > 0) {
    vs = "+" + vs;
  }

  if (format && format.length > 3) {
    vs = UiText.get(format, vs);
  }

  return vs;
}

function fEnc(s) {
  return parseInt(Math.round(new Number(s).valueOf() * 1000).toString()).toString(16);
}

function fDec(s) {
  return (new Number(parseInt(s, 16) / 1000).valueOf());
}

function iEnc(s) {
  return parseInt(s).toString(16);
}

function iDec(s) {
  return parseInt(s, 16);
}

var EFloat = function(s) {
  this.s = s;
  this.val = typeof s == 'string' ? fDec(s) : s;
};

var EInt = function(s) {
  this.s = s;
  this.val = typeof s == 'string' ? iDec(s) : s;
};

function isTrue(s) {
  return (s && ('' + s) == 'true') || s === true;
}

var BrowserDetect = {
  init: function () {
    this.browser = this.searchString(this.dataBrowser) || "An unknown browser";
    this.version = this.searchVersion(navigator.userAgent)
                     || this.searchVersion(navigator.appVersion)
      || "an unknown version";
    this.OS = this.searchString(this.dataOS) || "an unknown OS";
  },
  searchString: function (data) {
    for (var i = 0; i < data.length; i++) {
      var dataString = data[i].string;
      var dataProp = data[i].prop;
      this.versionSearchString = data[i].versionSearch || data[i].identity;
      if (dataString) {
        if (dataString.indexOf(data[i].subString) != -1) {
          return data[i].identity;
        }
      }
      else {
        if (dataProp) {
          return data[i].identity;
        }
      }
    }
  },
  searchVersion: function (dataString) {
    var index = dataString.indexOf(this.versionSearchString);
    if (index == -1) return;
    return parseFloat(dataString.substring(index + this.versionSearchString.length + 1));
  },
  dataBrowser: [
    {
      string: navigator.userAgent,
      subString: "Chrome",
      identity: "Chrome"
    },
    {         string: navigator.userAgent,
      subString: "OmniWeb",
      versionSearch: "OmniWeb/",
      identity: "OmniWeb"
    },
    {
      string: navigator.vendor,
      subString: "Apple",
      identity: "Safari",
      versionSearch: "Version"
    },
    {
      prop: window.opera,
      identity: "Opera"
    },
    {
      string: navigator.vendor,
      subString: "iCab",
      identity: "iCab"
    },
    {
      string: navigator.vendor,
      subString: "KDE",
      identity: "Konqueror"
    },
    {
      string: navigator.userAgent,
      subString: "Firefox",
      identity: "Firefox"
    },
    {
      string: navigator.vendor,
      subString: "Camino",
      identity: "Camino"
    },
    {                // for newer Netscapes (6+)
      string: navigator.userAgent,
      subString: "Netscape",
      identity: "Netscape"
    },
    {
      string: navigator.userAgent,
      subString: "MSIE",
      identity: "Explorer",
      versionSearch: "MSIE"
    },
    {
      string: navigator.userAgent,
      subString: "Gecko",
      identity: "Mozilla",
      versionSearch: "rv"
    },
    {                 // for older Netscapes (4-)
      string: navigator.userAgent,
      subString: "Mozilla",
      identity: "Netscape",
      versionSearch: "Mozilla"
    }
  ],
  dataOS : [
    {
      string: navigator.platform,
      subString: "Win",
      identity: "Windows"
    },
    {
      string: navigator.platform,
      subString: "Mac",
      identity: "Mac"
    },
    {
      string: navigator.userAgent,
      subString: "iPhone",
      identity: "iPhone/iPod"
    },
    {
      string: navigator.platform,
      subString: "Linux",
      identity: "Linux"
    }
  ]

};
BrowserDetect.init();

function browserIsIE() {
  return BrowserDetect.browser == "Explorer";
}

function browserIsIE9() {
  return navigator.userAgent.indexOf("Trident/5") > -1;
}

function isBadBrowser() {
  var b = BrowserDetect.browser;
  var v = BrowserDetect.version;
  var r = false;
  if (b == "Explorer") {
    r = v < 9;
  }
  return r;
}

function measureBrowserSpeed() {
  if (typeof ibeBenchmark == "function") {
    var t = new Timer("benchmark");
    t.start();
    ibeBenchmark();
    t.stop();
    return t.result();
  } else {
    ibewarning("Tried to measure browser speed, but benchmark library has not been included.");
    return undefined;
  }
}

var __isSlowBrowser = {};

function isSlowBrowser() {
  if (!__isSlowBrowser.result) {
    __isSlowBrowser.result = measureBrowserSpeed();
  }
  return  __isSlowBrowser.result > 100;
}

var __scheduler = {};

/**
 * This function works like runSoon, except you give the timer a name, and you can then overwrite it. Used in XS filter,
 * when clicking on any fields, the timer starts, if nothing happens in 1 sec, a search is triggered. If a new field
 * is interacted with, the timer is aborted and a new timer is triggered.
 * Arguments: (name: type [default value])
 * overwrite: true/false [true], if false, already existing timers will not be overwritten.
 * name: string ["default"], the name of the timer. Use same name again to overwrite the first timer.
 * time: int [10], the time in milliseconds before the function is triggered.
 * @param func
 * @param args
 */
function schedule(func, args) {
  args = $.extend({
                    overwrite:true,
                    name:"default",
                    time:10
                  }, args);
  var name = args.name;
  var time = args.time;
  __scheduler[name] = {};
  var now = new Date();
  var targetTime = now.getTime() + time;
  if (args.overwrite || __scheduler[name].targetTime === undefined) {
    __scheduler[name].targetTime = targetTime;
    __scheduler[name].func = func;
    setTimeout(function() {
      if (__scheduler[name].targetTime == targetTime) {
        // Nothing has changed, just run it. Otherwise other setTimeout will trigger the function.
        __scheduler[name].func();
        // Reset the scheduled item
        __scheduler[name].targetTime = undefined;
        __scheduler[name].func = undefined;
      }
    }, time);
  }
}

function sendKeyEvent(eventType, characterToPress, targetElement) {
  var evObj = undefined;
  if (window.KeyEvent) {
    evObj = document.createEvent('KeyEvents');
    evObj.initKeyEvent(eventType, true, true, window, false, false, false, false, characterToPress, 0);
    console.log('KeyEvent');
  } else {
    console.log('UIEvent');
    evObj = document.createEvent('UIEvents');
    evObj.initUIEvent(eventType, true, true, window, 1);
    evObj.keyCode = characterToPress;
  }
  targetElement.dispatchEvent(evObj);
}

function addTooltipToDomIfNotExists(className, id) {
  if (!getObj(id)) {
    var div = document.createElement("div");
    div.id = id;
    div.className = className;
    document.body.appendChild(div);
  }
}

function overlib(text, element, className, effect) {
  if (!className) className = "overlibTooltip";
  if (!effect) effect = "slide";
  $(element).attr("title", text);
  $(element).tooltip({
                       tipClass:className,
                       effect: effect
                     });
  $(element).removeAttr("title");
  // Show it immediately.
  var api = $(element).data("tooltip");
  api.show();
}

function nd(element) {
  $(element).tooltip("destroy");
}

function setOverLibStartHeight(h) {
  if (stringIsNumeric(h)) {
    // Set height
  } else {
    if (h.toLowerCase().trim() == "auto") {

    } else {
      ibewarning("Invalid overlib start height set: " + h);
    }
  }
}

function copyObject(sourceObject, targetObject) {
  if (targetObject === undefined) targetObject = {};
  for (prop in sourceObject) {
    var t = typeof(sourceObject[prop]);
    if (t === "string" || t === "number" || t === "undefined" || t === "boolean" || t === "object") {
      // Allow undefined properties, they don't do any harm.
      targetObject[prop] = sourceObject[prop];
    } else {
      // No warning message for functions.
      if (t !== "function") ibewarning("Cloning object, skipping property '" + prop + '", invalid type. Typeof=' + t);
    }
  }
  return targetObject;
}

function sendRequest(url, successFunction, failFunction, args) {
  if (args === undefined) args = {};
  var processManager = args.processManager;

  var p = new IBERequestProcess(url, successFunction, failFunction, args);

  if (processManager) {
    processManager.addProcess(p);
  } else {
    p.startProcess();
  }
}


function sendPostRequest(url, postData, successFunction, failFunction, args) {
  args = $.extend({method:"post"}, args);

  if (postData) args.postData = postData;
  var processManager = args.processManager;

  var p = new IBERequestProcess(url, successFunction, failFunction, args);

  if (processManager) {
    processManager.addProcess(p);
  } else {
    p.startProcess();
  }
}

function sendFormRequest(form, successFunction, failFunction, args) {
  //function sendFormRequest(form, successFunction, failFunction, customAction, customObject, loadAnimation, forceAutoMessage, disableAlerts) {
  if (args === undefined) args = {};
  if (!args.customAction) args.customAction = form.action;
  var postData = $(form).serialize();
  sendPostRequest(args.customAction, postData, successFunction, failFunction, args);
}

/**
 * A request which uses the same mechanics as sendRequest, etc. Which means that callback method receives a predefined object with message, resultType, model, etc.
 * @param url
 * @param successFunction
 * @param failureFunction
 * @param args
 */
function IBERequestProcess(url, successFunction, failureFunction, args) {
  this.args = args = $.extend({
                                customObject:undefined, // Used to pass data to callback functions.
                                resultReference:undefined, // If set, response model properties will be copied to this object.
                                method:"get"
                              }, args);
  validateArgument(args.method, {rules:{allowUndefined:false, requireValueIsOneOf:["post", "get"]}}, "args.method");

  this.postData = args.postData;
  this.method = args.method;
  this.successFunction = successFunction;
  this.failureFunction = failureFunction;
  this.cancelFunction = args.cancelFunction;
  this.resultReference = args.resultReference;
  this.isAllowedToCancel = args.isAllowedToCancel || args.isAllowedToCancel === undefined ? true : false;
  this.customObject = args.customObject;
  this.forceAutoMessage = args.forceAutoMessage;
  this.disableAlerts = args.disableAlerts;
  this.id = args.id;

  this.isRunning = false;
  this.isCompleted = false;
  this.isCancelled = false;
  this.evaluateResponse = false;

  // Is aware of its manager, so it can callback when it is finished. These values are set by the manager when the process is added.
  this.parentManager = undefined;
  this.processIndex = undefined;

  this.startProcess = function() {
    if (this.isRunning === false) {
      this.isRunning = true;
      this.isCompleted = false;
      this.isCancelled = false;

      var that = this;
      $.ajax({
               url: url,
               dataType: 'json',
               data:this.postData,
               type:this.method.toUpperCase(),
               success: function(response, textStatus, XMLHttpRequest) {
                 var p = that;
                 if (response == null) {
                   ibeerror("Trying to execute RPC, but got null as response. Connection failed? URL: " + url);
                 }
                 if (!p.isCancelled) {
                   p.isCompleted = true;
                   p.isRunning = false;
                   if (p.resultReference && response.model) {
                     copyObject(response.model, p.resultReference);
                   }

                   // Start callback to manager first, new processes are triggered and HTTP request will run while more callbacks are executed.
                   if (p.parentManager && p.parentManager.processDoneCallback) {
                     p.parentManager.processDoneCallback(p);
                   }
                   // Do rest

                   if (p.forceAutoMessage === true && response.message) ibealert(response.message, !p.disableAlerts);

                   if (response && (response.resultType === 'OK' || response.resultType === 'SUCCESS')) {
                     if (!p.forceAutoMessage) ibealert(response.message, !p.disableAlerts);
                     p.successFunction.apply(p, [response, p.customObject]);
                   } else {
                     // Not "OK", exception in server request
                     if (response == null) {
                       var message = 'Could not connect to server, please try again later. If the problem persists, please contact us.';
                       response = {message:message};
                     }
                     if (p.failureFunction && typeof p.failureFunction == 'function') {
                       p.failureFunction.apply(p, [response, p.customObject]);
                     } else {
                       ibewarning("RPC request failed: " + url);
                       if (!p.forceAutoMessage) ibealert(response.message, p.disableAlerts !== true);
                     }
                   }
                 }
               },
               error: function(request, textStatus, error) {
                 var p = that;
                 // Invalid JSON received, there is no response or response.message.
                 if (!p.isCancelled) {
                   p.isCompleted = true;
                   p.isRunning = false;
                   // Start callback to manager first, new processes are triggered and HTTP request will run while more callbacks are executed.
                   if (p.parentManager && p.parentManager.processDoneCallback) {
                     p.parentManager.processDoneCallback(p);
                   }

                   var message = 'An internal system error occurred, please try again later. If the problem persists, please contact us.';
                   var response = {message:message};
                   if (p.failureFunction && typeof p.failureFunction === 'function') {
                     p.failureFunction.apply(p, [response, p.customObject, error]);
                   } else {
                     ibewarning("RPC connection failed: " + url);
                     ibealert(message, !p.disableAlerts);
                   }
                 }
               }
             });
    }
  };

  this.cancelProcess = function() {
    if (this.isRunning) {
      //ibelogs("this.cancelProcess");
      if (this.isAllowedToCancel) {
        this.isRunning = false;
        this.isCancelled = true;
        // Start callback to manager first, new processes are triggered and HTTP request will run while more callbacks are executed.
        if (this.parentManager && this.parentManager.processDoneCallback) {
          this.parentManager.processDoneCallback(this);
        }
        if (this.cancelFunction) {
          this.cancelFunction.apply(this.callbackScope ? this.callbackScope : this, [o, this, this.customObject]);
        }
      } else {
        ibewarning("Trying to cancel request process, but cancel is not allowed.");
      }
    }
    this.isCompleted = false;
  };
}

/**
 * A barebone process. Doesn't do anything with the response from the server.
 * @param url
 * @param resultReference
 * @param successCallbackFunction
 * @param failureCallbackFunction
 * @param cancelCallbackFunction
 * @param parameters
 * @param method
 * @param callbackScope
 * @param parentObject
 */
function IBEProcess(url, resultReference, successCallbackFunction, failureCallbackFunction, cancelCallbackFunction,
                    parameters, method, callbackScope, parentObject) {

  this.url = url;
  this.parameters = parameters;
  this.method = method ? method : 'GET';
  this.successFunction = successCallbackFunction;
  this.failureFunction = failureCallbackFunction;
  this.cancelFunction = cancelCallbackFunction;
  this.callbackScope = callbackScope;
  this.resultReference = resultReference;
  this.isRunning = false;
  this.isCompleted = false;
  this.isCancelled = false;
  this.parentObject = parentObject;
  this.evaluateResponse = false;

  // Is aware of its manager, so it can callback when it is finished.
  this.parentManager = undefined;
  this.processIndex = undefined;

  this.copyToReference = function (o) {
    if (this.resultReference) {
      if (this.evaluateResponse) {
        try {
          var res = eval(o.responseText);
          for (var prop in res) {
            this.resultReference[prop] = res[prop];
          }
        } catch (e) {
          ibeerror('Unable to evaluate and store process JSON response in reference object.');
          ibelogs(e);
        }
      } else {
        this.resultReference.responseText = o.responseText;
        this.resultReference.responseXML = o.responseXML;
        this.resultReference.statusText = o.responseXML;
        this.resultReference.argument = o.argument;
        this.resultReference.getAllResponseHeaders = o.getAllResponseHeaders;
        this.resultReference.getResponseHeader = o.getResponseHeader;
        this.resultReference.statusText = o.statusText;
        this.resultReference.status = o.status;
        this.resultReference.tId = o.tId;
      }
    }

  };

  this.localSuccess = function(o) {
    if (!this.isCancelled) {
      this.isCompleted = true;
      this.isRunning = false;
      this.copyToReference(o);
      // Start callback to manager first, new processes are triggered and HTTP request will run while more callbacks are executed.
      if (this.parentManager && this.parentManager.processDoneCallback) {
        this.parentManager.processDoneCallback(this);
      }
      // Run external callback last.
      if (this.successFunction) {
        this.successFunction.apply(this.callbackScope ? this.callbackScope : this, [o, this, this.parentObject]);
      }
    }
  };

  this.localFailure = function(o) {
    if (!this.isCancelled) {
      this.isCompleted = true;
      this.isRunning = false;
      // Start callback to manager first, new processes are triggered and HTTP request will run while more callbacks are executed.
      if (this.parentManager && this.parentManager.processDoneCallback) {
        this.parentManager.processDoneCallback(this);
      }
      if (this.failureFunction) {
        this.failureFunction.apply(this.callbackScope ? this.callbackScope : this, [o, this, parentObject]);
      }
    }
  };

  this.localCancel = function(o) {
    if (this.isRunning) {
      this.isRunning = false;
      this.isCancelled = true;
      // Start callback to manager first, new processes are triggered and HTTP request will run while more callbacks are executed.
      if (this.parentManager && this.parentManager.processDoneCallback) {
        this.parentManager.processDoneCallback(this);
      }
      if (this.cancelFunction) {
        this.cancelFunction.apply(this.callbackScope ? this.callbackScope : this, [o, this, parentObject]);
      }
    }
  };

  this.startProcess = function() {
    if (this.isRunning === false) {
      var u = this.url;
      if (this.method == 'GET') {
        var time = new Date().getTime();
        var key = time / 15;
        u += (((u.indexOf('?') == -1) ? '?' : '&') + ('b' + key + '=' + time));
      }
      this.isRunning = true;
      this.isCompleted = false;
      this.isCancelled = false;
      YAHOO.util.Connect.asyncRequest(this.method, u, {
        success:this.localSuccess,
        failure:this.localFailure,
        scope: this
      }, this.method == 'POST' ? this.parameters : null);
    }
  };

  this.cancelProcess = function() {
    if (this.isRunning) {
      this.localCancel();
    }
    this.isCompleted = false;
  };
}

function IBEProcessManager(pmode, allDoneCallback, callbackScope) {

  /**
   * Mode:
   * "pushover" = add process and all current processes are cancelled and removed. Only one is allowed.
   * "allowone" = add process only if no process is running.
   * "seq" = one at the time, in priority order
   * "allowall" = all processes can be run freely and independently. They will run directly when added.
   *
   * default: allowall
   */

  this.possibleModes = ["allowall", "pushover", "allowone", "seq"];
  this.mode = pmode ? pmode : this.possibleModes[0];
  if (!this.possibleModes.contains(this.mode)) {
    ibewarning("Creating process manager, but the selected mode (" + this.mode + ") is not valid. Use only " + this.possibleModes.join("/") + ".");
  }
  this.queueMaxSize = 10;
  this.processesRunning = 0;
  this.queueFullAction = 'discard'; // 'discard' or 'expand', defaults to discard
  this.allDoneCallback = allDoneCallback;
  this.callbackScope = callbackScope;

  // private:
  this.processQueue = new Array();
  this.priorityList = new Array();

  this.addProcessList = function(list) {
    var i;
    for (i = 0; i < list.length; i++) this.addProcess(list[i], undefined, true);
    for (i = 0; i < list.length; i++) this.triggerScheduler(); // Trigger all of them, AFTER adding them.
  };

  this.addProcess = function (process, priority, disableTrigger) {
    if (this.processQueue.length >= this.queueMaxSize) {
      if (this.queueFullAction == 'expand') {
        this.queueMaxSize *= 2; // Double the size of the queue.
      } else {
        // Discard the process
        return;
      }
    }
    if (priority === undefined) {
      priority = 0; // if not specified, no priority!
    }
    if (this.mode == 'pushover') {
      this.clearQueue();
    } else {
      if (this.mode == 'allowone' && this.processesRunning > 0) {
        return; // Only one process allowed, and it is already running. Can be implemented with queueMaxSize as well..
      }
    }
    var i = this.findAvailableProcessIndex();
    this.processQueue[i] = process;
    this.priorityList[i] = priority;
    process.processIndex = i;
    process.parentManager = this;
    if (disableTrigger === undefined || disableTrigger === false) {
      this.triggerScheduler();
    }
    return i;
  };

  this.triggerScheduler = function () {
    var i = this.findHighestPriorityProcessIndex();
    if (this.mode == 'pushover' ||
        this.mode == 'allowall' ||
        (this.mode == 'allowone' && this.processesRunning < 1) ||
        (this.mode == 'seq' && this.processesRunning < 1)) {
      if (i >= 0) {
        this.processesRunning++;
        this.processQueue[i].startProcess();
      }
    }
  };

  this.findHighestPriorityProcessIndex = function () {
    var running = 0; // count running processes at the same time.
    var highestPrio = -1;
    var highestPrioIndex = -1;
    for (var i = 0; i < this.processQueue.length; i++) {
      if (this.processQueue[i]) {
        if (this.processQueue[i].isRunning) {
          running++;
        } else {
          if (this.priorityList[i] > highestPrio) {
            highestPrioIndex = i;
            highestPrio = this.priorityList[i];
          }
        }
      }
    }
    this.processesRunning = running;
    return highestPrioIndex;
  };

  this.findAvailableProcessIndex = function () {
    for (var i = 0; i < this.processQueue.length; i++) {
      if (this.processQueue[i] === undefined) {
        break;
      }
    }
    return i;
  };

  this.removeProcess = function (i) {
    if (this.processQueue !== undefined) {
      if (this.processQueue[i] !== undefined) {
        if (this.processQueue[i].isRunning) {
          this.processesRunning--;
        }
        this.processQueue[i].cancelProcess();
      }

      this.processQueue.splice(i, 1); // remove process from queue, length will decrease.
      // Update index of all processes
      for (; i < this.processQueue.length; i++) {
        this.processQueue[i].processIndex = i;
      }
      return true;
    }
    return false;
  };

  this.cancelAllProcesses = function () {
    for (var i = 0, l = this.processQueue.length; i < l; i++) {
      this.cancelProcessWithIndex(i);
    }
  };

  /**
   * Cancels the one process with given index.
   * @param i
   */
  this.cancelProcessWithIndex = function (i) {
    if (i && this.processQueue) {
      if (this.processQueue[i].isRunning) {
        this.processQueue[i].cancelProcess();
        this.processesRunning--;
      }
    }
  };

  /**
   * Cancels all processes with a given id.
   * @param id
   */
  this.cancelProcessWithId = function(id) {
    if (id && this.processQueue) {
      for (var i = 0, l = this.processQueue.length; i < l; i++) {
        var p = this.processQueue[i];
        if (p.isRunning && p.id === id) {
          p.cancelProcess();
          this.processesRunning--;
          break;
        }
      }
    }
  };

  /**
   * Returns the first process found with given id. If none is found, null is returned.
   * @param id
   */
  this.getProcessWithId = function(id) {
    if (this.processQueue) {
      for (var i = 0, l = this.processQueue.length; i < l; i++) {
        var p = this.processQueue[i];
        if (p.isRunning && p.id === id) {
          return p;
        }
      }
    }
    return null;
  };

  this.clearQueue = function () {
    for (var i = 0, l = this.processQueue.length; i < l; i++) {
      this.removeProcess(i);
    }
  };

  this.clearIdleProcesses = function () {
    for (var i = 0, l = this.processQueue.length; i < l; i++) {
      if (this.processQueue[i]) {
        if (this.processQueue[i].isRunning) {
          if (this.removeProcess(i)) {
            i--;
          }
        }
      }
    }
  };

  this.processDoneCallback = function (process) {
    this.processesRunning--;
    this.removeProcess(process.processIndex);

    if (this.processQueue.length === 0) {
      if (typeof this.allDoneCallback == 'function') {
        this.allDoneCallback();
      }
    } else {
      this.triggerScheduler();
    }

  };
}

/**
 * Takes all fields of a form and assembles them into a GET formatted string with all variable names and values.
 * Example: id=35&name=mattias&age=12
 * @param form The form to use.
 */
function formParametersToStringFormat(form) {
  if (!form) {
    ibeerror('Form element is null.');
    return null;
  }
  var parameters = new RequestParameters({form:form});
  return parameters.toString();
}

function RequestParameters(args) {
  args = $.extend({
                    form:undefined,
                    includeEmptyParameterValues : true
                  }, args);

  this.parameters = [];

  this.addParameter = function (parameterName, parameterValue, _args) {
    _args = $.extend({allowMoreThanOneValue:false}, _args);
    if (!_args.allowMoreThanOneValue) {
      this.removeParameter(parameterName);
    }
    this.pushParameter(parameterName, parameterValue);
  };

  this.addParameterObject = function(obj, _args) {
    var that = this;
    $.each(obj, function(key, member) {
      var t = typeof member;
      if (t === "string" || t === "number") that.addParameter(key, member, _args);
    });
  };

  this.addListParameter = function (parameterName, parameterValue) {
    this.removeParameter(parameterName);
    if (parameterValue && parameterValue.length) {
      for (i = 0; i < parameterValue.length; i++) {
        this.pushParameter(parameterName, parameterValue[i]);
      }
    }
  };

  this.pushParameter = function (parameterName, parameterValue) {
    this.parameters.push({name:parameterName, value:parameterValue});
  };

  this.mergeParameterObject = function (parameterObject) {
    for (var i = 0; i < parameterObject.parameters.length; i++) {
      this.parameters.push(parameterObject.parameters[i]);
    }
  };

  this.getParameter = function (parameterName) {
    for (var i = 0; i < this.parameters.length; i++) {
      if (this.parameters[i].name == parameterName) return this.parameters[i];
    }
  };

  this.getParameters = function(parameterName) {
    var params = [];
    for (var i = 0, numParams = this.parameters.length; i < numParams; i++) {
      var parameter = this.parameters[i];
      if (parameter.name === parameterName) {
        params.push(parameter);
      }
    }
    return params;
  };

  this.removeParameter = function (parameterName) {
    for (var i = 0; i < this.parameters.length; i++) {
      // Might cause a problem if there are more parameters of the same name, which is fully possible
      if (this.parameters[i].name == parameterName) return this.parameters.splice(i, 1);
    }
  };

  this.removeParameterWithValue = function (parameterName, parameterValue) {
    for (var i = 0; i < this.parameters.length; i++) {
      // Might cause a problem if there are more parameters of the same name, which is fully possible
      if (this.parameters[i].name == parameterName && this.parameters[i].value == parameterValue) return this.parameters.splice(i, 1);
    }
  };

  this.getParameterValue = function (parameterName) {
    var p = this.getParameter(parameterName);
    return p ? p.value : null;
  };

  this._addArrayReadyObject = function(obj) {
    this.parameters.push(obj);
  };

  this.clone = function() {
    var i = 0, size = this.parameters.length, cloned = new RequestParameters();
    for (; i < size; i++) {
      // not a perfect clone (need to deep copy really - assuming users will replace parameters). see if this enough.
      cloned._addArrayReadyObject(this.parameters[i]);
    }
    return cloned;
  };

  if (args.form) {
    var es = args.form.elements;
    if (nullOrUndefined(es)) {
      ibeerror('No elements attribute in formElement with id=' + args.form.id);
      return;
    }
    for (var i = 0; i < es.length; i++) {
      var inputElement = es[i], value = inputElement.value;
      if (!args.includeEmptyParameterValues && value === "") {
        continue;
      }
      if ((inputElement.type == "radio" || inputElement.type == "checkbox")) {
        if (inputElement.checked) this.pushParameter(inputElement.name, value);
      } else {
        this.pushParameter(inputElement.name, value);
      }
    }
  }

}

RequestParameters.prototype.toString = function() {
  var output = [];
  for (var i = 0, length = this.parameters.length; i < length; i++) {
    var parameter = this.parameters[i], value = parameter.value;
    if (value && (typeof value === "string" || typeof value === "double" || typeof value === "float")) {
      value = encodeURIComponent(value);
    }
    if (value && value !== 'undefined') {
      output.push(parameter.name + "=" + value);
    }
  }
  return output.join("&");
};

function isValidHtml(html, args) {
  args = $.extend({}, args);
  var r = validateHtml(html, args);
  return r.ok;
}


/**
 * Validates a string containing HTML code. You can specify some options and it returns an object with the result.
 *
 * Result:
 * result.ok = true or false, true if there are no criticals or errors. warnings are allowed.
 * result.perfect = true or false, true if the html was perfectly formed.
 * result.warnings = list of strings containing the warning
 * result.errors = list of strings containing the errors
 * result.criticals = list of strings containing critical errors. these are errors that may harm the system and should prevent the user from storing the html.
 * result.hasWarnings
 * result.hasErrors
 * result.hasCriticals
 *
 * The options object can contain the following:
 * removeScriptTags = true or false, if true, all script tags will be removed (requires the tag to be lower case!)
 * allowHtmlTag = true or false, if true, fails validation if it finds a html tag.
 * allowBodyTag = true or false, if true, fails validation if it finds a body tag.
 * allowTitleTag = true or false, if true, fails validation if it finds a title tag.
 * allowMso = true or false, if false, fails validation if it finds "if gte mso".
 * allowWordDocumentTag = true or false, if false, fails validation if it finds a <w:WordDocument> tag.
 * allowXmlTag = true or false, if false, fails validation if it finds <xml> tag, which is used by Microsoft Word.
 * allowHtmlComments = true or false
 * requireCorrectTables = true or false, if true
 *
 * @param html
 * @param args
 */
function validateHtml(html, args) {



  // TODO: <!-- måste den ju klara!


  args = $.extend({
                    allowMso:false,
                    allowWordDocumentTag:false,
                    allowHtmlComments:true
                  }, args);
  var i;

  if (args.removeScriptTags) {
    var jsSplit = html.splitTwice('<script type="text/javascript">', '</script>');
    html = '';
    for (i = 0; i < jsSplit.length; i += 2) {
      html += jsSplit[i];
    }
  }

  var stack = new Array();
  var result = new Object();
  result.warnings = new Array();
  result.errors = new Array();
  result.criticals = new Array();
  result.ok = true;
  result.saveAllowed = true;

  var split = html.splitTwice("<", ">");
  result.containsHtml = split.length > 1;

  for (i = 1; i < split.length; i += 2) {
    var tag = split[i].toLowerCase();
    var type = tag.split(" ")[0]; // Remove all after first space
    if (tag.startsWith("/")) {
      if (stack.length <= 0) {
        result.errors.push("Incomplete HTML, not enough closing tags.");
        break;
      }
      type = type.substring(1);
      var peek = stack[stack.length - 1];
      while (peek !== type && stack.length > 0) {
        stack.pop();
        checkCloseRequirement(peek, args, result);
        peek = stack[stack.length - 1];
      }
      if (stack.length == 0) {
        result.errors.push("Trying to close " + type + " tag, but there is no matching start tag.");
        break;
      }
      stack.pop();
    } else {
      if (tag.endsWith("/")) {
        // Do nothing, it closes itself.
      } else {
        // Start tag
        var error = false;
        error = (type == "html" && args.allowHtmlTag === false) || error;
        error = (type == "body" && args.allowBodyTag === false) || error;
        error = (type == "title" && args.allowTitleTag === false) || error;
        error = (type == "w:WordDocument" && args.allowWordDocumentTag === false) || error;
        error = (type == "xml" && args.allowXmlTag === false) || error;
        if (error) {
          result.errors.push("Found " + type + " tag, " + type + " tag not allowed.");
          break;
        }

        stack.push(type);
      }
    }
  }

  while (stack.length > 0) {
    checkCloseRequirement(stack.pop(), args, result);
  }

  if (args.allowMso && html.contains("if gte mso")) {
    result.errors.push("Found 'if gte mso' from Microsoft Office 2000 which is not allowed.");
  }

  result.hasErrors = result.errors.length != 0;
  result.hasWarnings = result.warnings.length != 0;
  result.hasCriticals = result.criticals.length != 0;
  result.saveAllowed = result.criticals.length == 0;
  result.ok = !result.hasErrors && !result.hasCriticals;
  result.perfect = !result.hasErrors && !result.hasCriticals && !result.hasWarnings;
  return result;
}

function checkCloseRequirement(type, options, result) {
  var target = result.errors;
  switch (type) {
    case "table":
    case "tr":
    case "td":
      if (options.requireCorrectTables) {
        target = result.criticals;
      }
      break;
    case "br":
    case "img":
      target = result.warnings;
      break;
    default:

  }
  target.push(type + " tag is not closed properly.");
}

function renderHtmlValidationResult(result) {

  if (!result.containsHtml) return "";

  // Icon
  var color = "#55dd55";
  if (result.hasWarnings) color = "yellow";
  if (result.hasErrors) color = "#ff5555";
  if (result.hasCriticals) color = "#ff9999";
  var icon = '<div style="background-color:' + color + '; height:12px;width:12px;">&nbsp;</div>';

  // Text
  var text = "";
  if (result.perfect) {
    text += "HTML looks OK!";
  }
  else {
    if (result.hasCriticals) {
      text += "HTML is in <b>critical</b> state. You should <b>NOT save</b>! Saving might cause problems in the system and might not easily be fixed.";
    }
    else {
      if (result.hasErrors) {
        text += "HTML contains errors. You really should fix them before saving, saving now might mess up how the site looks.";
      }
      else {
        if (result.hasWarnings) text += "HTML contains warnings. Don't worry, it should be OK to save but you should fix the problems so that there is no bad HTML.";
      }
    }
  }

  var html = '<table>';
  var i;
  html += '<tr><td>' + icon + '</td><td>' + text + '</td></tr>';
  for (i = 0; i < result.warnings.length; i++) html += '<tr><td>&nbsp;</td><td><b>Warning:</b> ' + result.warnings[i] + '</td></tr>';
  for (i = 0; i < result.errors.length; i++) html += '<tr><td>&nbsp;</td><td><b>Error:</b> ' + result.errors[i] + '</td></tr>';
  for (i = 0; i < result.criticals.length; i++) html += '<tr><td>&nbsp;</td><td><b>Critical: ' + result.criticals[i] + '</b></td></tr>';
  html += '</table>';
  return html;

}

function setupHtmlValidationOnTextArea(textAreaId) {
  var targetTextElement = document.createElement("div");
  targetTextElement.id = textAreaId + "_html_validation_message";
  var area = $("#" + textAreaId);
  area.parent().append(targetTextElement);
  area.bind("keyup", function() {
    validateHtmlInTextArea(textAreaId, targetTextElement.id);
  });
  triggerHtmlValidationUpdate(textAreaId);
}

function validateHtmlInTextArea(textAreaId, targetTextElementId) {
  var html = getObj(textAreaId).value;
  var result = validateHtml(html, {
    requireCorrectTables: true,
    allowHtmlTag: false,
    allowBodyTag: false,
    allowTitleTag :false
  });
  var r = renderHtmlValidationResult(result);
  $("#" + targetTextElementId).html(r);
}

function triggerHtmlValidationUpdate(textAreaId) {
  targetTextElementId = textAreaId + "_html_validation_message";
  validateHtmlInTextArea(textAreaId, targetTextElementId);
}

function showHtmlValidation(textAreaId) {
  $("#" + textAreaId + "_html_validation_message").show();
}

function hideHtmlValidation(textAreaId) {
  $("#" + textAreaId + "_html_validation_message").hide();
}

/**
 * Fetches the location of the user using the browsers built-in GPS/location services.
 * @param doneCallback Executed if success, passes a position object as parameter containing lat and long.
 * @param failCallback Executed if failed, passes no parameters.
 * @param noSupportCallback Executed if browser doesn't support location services.
 */
function getBrowserGeoLocation(doneCallback, failCallback, noSupportCallback) {
  if (navigator.geolocation) {
    try {
      navigator.geolocation.getCurrentPosition(function(position) {
        if (doneCallback && typeof doneCallback === "function") doneCallback(position);
      }, function() {
        if (failCallback && typeof failCallback === "function") failCallback();
      });
    } catch (e) {
      ibewarning("Exception when using browser geolocation:");
      ibewarning(e);
    }
    // Try Google Gears Geolocation
  } else {
    if (google.gears) {
      var geo = google.gears.factory.create('beta.geolocation');
      geo.getCurrentPosition(function(position) {
        if (doneCallback && typeof doneCallback === "function") doneCallback(position);
      }, function() {
        if (failCallback && typeof failCallback === "function") failCallback();
      });
      // Browser doesn't support Geolocation
    } else {
      if (noSupportCallback && typeof noSupportCallback === "function") noSupportCallback();
    }
  }

}

function openProgressBarDialogueBox(args) {
  args = $.extend({
                    isProgress: true,
                    picture: undefined,
                    showCloseIcon: true,
                    copyContentFrom : $("#progressBarContentContainerPlaceHolder").children()[0], // First child, this is the container div for the container. We do not want to copy it, it has a unique id.
                    textAndCopy : true,
                    enableIcon : false,
                    resizable:true,
                    height:200
                  }, args);
  openDialogueBox(args);
}


/**
 * Opens a dialogue box. Requires jQuery UI.
 * See $.extend(..) to see all args you can use.
 */
function openDialogueBox(args) {
  args = $.extend({
                    isProgress: false,
                    title:"Are you sure?",
                    text:"What is going on?",
                    picture: undefined,
                    showCloseIcon: true,
                    copyContentFrom:undefined,
                    textAndCopy:false, // Both text and copyContentFrom will be used.
                    textFirst:true, // Which content comes first, text or copyContentFrom?
                    resizable:false,
                    enableIcon:true, // Shows a small icon left of body text.
                    height:140,
                    width:300,
                    copiedContentCss:undefined, // CSS class applied to div around the copied content (if any)
                    copiedContentStyle:undefined // CSS style applied to div around the copied content (if any)
                  }, args);

  var imageHtml = "";
  if (args.picture && args.isProgress) {/* is not currently using textFirst param.*/
    imageHtml = '<div class="progressImage mt8 mb8 center"><img src="' + args.picture + '"/></div>';
  }

  var dialogueHtml = '<div id="dialogueBoxDiv" title="Are you sure?" class="center mb8" style="display:none;">' +
                     imageHtml +
                     '<span id="dialogueBoxIcon" class="ui-icon ui-icon-alert left" style="margin:0 7px 20px 0;"></span>' +
                     '<span id="dialogueBoxDivText"></span>' +
                     '</div>';

  if (!getObj('dialogueBoxDiv')) {
    $('body').append(dialogueHtml);
  } else {
    $("#dialogueBoxDiv").dialog("destroy"); // Restore it to pre-dialog state, with title attribute, etc.
  }

  if (!args.enableIcon) {$("#dialogueBoxIcon").hide();}

  if (args.title) {$("#dialogueBoxDiv").attr("title", args.title);}

  var copiedHtml = "";
  var baseHtml = "";
  if (args.copyContentFrom) {
    if (args.textAndCopy && args.textFirst) baseHtml += args.text;
    var copiedContentCss = args.copiedContentCss ? ' class="' + args.copiedContentCss + '"' : "";
    var copiedContentStyle = args.copiedContentStyle ? ' style="' + args.copiedContentStyle + '"' : "";
    baseHtml += '<div id="progressBarContentPlaceHolder" ' + copiedContentCss + copiedContentStyle + '></div>';
    if (args.textAndCopy && !args.textFirst) baseHml += args.text;

    copiedHtml += $(args.copyContentFrom).html();

    $("#dialogueBoxDivText").html(baseHtml);
    if (args.text) $("#progressBarContentPlaceHolder").html(copiedHtml);
  } else {
    if (args.text) $("#dialogueBoxDivText").html(args.text);
  }

  var dialogCssClass = "";
  if (!args.showCloseIcon) {dialogCssClass = "noCloseIcon";}

  $("#dialogueBoxDiv").dialog({
                                resizable: args.resizable,
                                zIndex:10100,
                                height:args.height,
                                modal: true,
                                buttons: args.buttons,
                                width:args.width,
                                dialogClass: dialogCssClass
                              });

}
/**
 * Used by hideProgressPanel().
 */
function closeDialogueBox() {
  $("#dialogueBoxDiv").dialog("close");
}

function decodeHtmlEntity(str) {
  try {
    var tarea = document.createElement('textarea');
    tarea.innerHTML = str;
    return tarea.value;
    tarea.parentNode.removeChild(tarea);
  }
  catch(e) {
    //for IE add <div id="htmlconverter" style="display:none;"></div> to the page
    document.getElementById("htmlconverter").innerHTML = '<textarea id="innerConverter">' + str + '</textarea>';
    var content = document.getElementById("innerConverter").value;
    document.getElementById("htmlconverter").innerHTML = "";
    return content;
  }
}

/*
 * Proposed usage:
 *
 * var x = new IbeMetrics('Links');
 *
 * <a href="/" onclick="x.inc('Click');">click here!</a>
 *
 * <a href="/" onmouseover="x.inc('Look');"> look at this ! </a>
 *
 * @param prefix A prefix, which all calls to inc(key) wil be
 *               prefixed with, for example:
 *               new IbeMetrics('APrefix').inc('Key');
 *               will generate a key named 'APrefix.Key'
 *               when recorded on the server.
 */
var IbeMetrics = Class.extend(
  {
    init:function(args) {
      this.args = args = $.extend({
                                    prefix:undefined
                                  }, args);
      this.prefix = args.prefix;
    },

    /**
     * Count given key prefixed by possible prefix
     *
     * @param key  Key to increment
     *
     * @return true if the event was sent to the server.
     */
    inc:function(key) {
      var fullKey = (this.prefix ? (this.prefix + (key ? '.' : '')) : '') + (key ? key : '');
      // Trigger event.
      try {
        var url = '/ibe-metric/' + fullKey;
        if (this.args.site) url += "?site=true";
        var image = new Image();
        image.src = url;
        return true;
      } catch (e) {}
      return false;
    }

  });

var IbeSiteMetrics = IbeMetrics.extend(
  {
    init:function(args) {
      args.site = true;
      this._super(args);
    }
  });

var getSiteName = function() {
  return IBE.siteName;
};

var getHostUrl = function(args) {
  args = $.extend({
                    secure:false,
                    prefixProtocol:true,
                    path:undefined,
                    port:undefined
                  }, args);
  validateArgument(args.secure, {rules:{allowUndefined:true, requireBoolean:true}}, "args.secure");
  validateArgument(args.prefixProtocol, {rules:{allowUndefined:true, requireBoolean:true}}, "args.prefixProtocol");
  validateArgument(args.path, {rules:{allowUndefined:true, requireString:true}}, "args.path");
  validateArgument(args.port, {rules:{allowUndefined:true, requireNumber:true}}, "args.port");

  var url = "";
  var port = args.port ? args.port : window.location.port;
  var isDefaultPort = port == "" || (port == "80" && !args.secure) || (port == "443" && args.secure);
  port = isDefaultPort ? "" : ":" + port;
  var path = args.path ? args.path : "";
  if (path && !path.startsWith("/")) path = "/" + path;
  if (args.prefixProtocol) url = ((args.secure) ? 'https://' : 'http://');
  return  url + window.location.hostname + port + path;
};

function loginIbeUser(email, password, successCallback, failCallback, args) {
  validateArgument(successCallback, {rules:{allowUndefined:false, requireFunction:true}}, "successCallback");
  validateArgument(failCallback, {rules:{allowUndefined:false, requireFunction:true}}, "failCallback");
  args = $.extend({}, args);
  var params = new RequestParameters({});
  params.addParameter("email", email);
  params.addParameter("password", password);
  var url = "/ajax.user.loginuser.do.action?" + params.toString();
  sendRequest(url, function(response) {
    if (successCallback && typeof successCallback === "function") successCallback(response);
  }, function(response, customObject, error) {
    if (failCallback && typeof failCallback === "function") failCallback(response, customObject, error);
  }, args);
}

function isWithinGoogleMapBound(lat, lng, mapBounds) {
  return lat < mapBounds.getNorthEast().lat() &&
         lat > mapBounds.getSouthWest().lat() &&
         lng < mapBounds.getNorthEast().lng() &&
         lng > mapBounds.getSouthWest().lng();
}

function logoutIbeUser(successCallback, failCallback) {
  var url = "/ajax.user.logoutuser.do.action";
  sendRequest(url, function(response) {
    if (successCallback && typeof successCallback === "function") successCallback(response);
  }, function(response, customObject, error) {
    if (failCallback && typeof failCallback === "function") failCallback(response, customObject, error);
  });
}

function cacheImage(url) {
  if (url) {
    var image = new Image();
    image.src = url;
  }
}

/**
 * This function is horrible and should only be used for test purposes!
 * @param delay milliseconds
 */
function sleep(delay) {
  if (isProdEnvironment()) return;
  var start = new Date().getTime();
  while (new Date().getTime() < start + delay) {

  }
}
/**
 * Converts a String representation of time to an int time.
 *
 * @param time in format HH:MM
 * @returns HHMM as an int value.
 */
function timeAsInt(time) {
  if (!time) {
    return undefined;
  } else {
    var s = removeInitCharacters(time.getHours().toString(), "0") + time.getMinutes().toString();
    return parseInt(s);
  }
}

function timeStringAsInt(time) {
  if (!time || time.indexOf(':') == -1) {
    return undefined;
  } else {
    var hhmm = time.split(':');
    var s = removeInitCharacters(hhmm[0], "0") + hhmm[1];
    return parseInt(s);
  }
}

function Statistics() {

  this.data = {};

}

Statistics.prototype.toString = function() {
  var s = "";
  for (key in this.data) {
    s += key + " : " + this.data[key] + "\n";
  }
  return s;
};

Statistics.prototype.get = function(name) {
  return this.data[name];
};

Statistics.prototype.set = function(name, value) {
  this.data[name] = value;
};

Statistics.prototype.clear = function(name) {
  this.data[name] = undefined;
};

Statistics.prototype.inc = function(name) {
  if (!this.data[name]) this.data[name] = 0;
  return this.data[name]++;
};

Statistics.prototype.dec = function(name) {
  if (!this.data[name]) this.data[name] = 0;
  return this.data[name]--;
};

function Timer(name) {

  this.name = name;

  this.state = "init";
  this.running = false;
  this.time = undefined;
  this.startTime = undefined;
  this.endTime = undefined;
}

Timer.prototype.start = function() {
  this.startTime = new Date();
  this.state = "running";
  this.running = true;
};

Timer.prototype.stop = function() {
  this.endTime = new Date();
  this.state = "stopped";
  this.running = false;
  this.time = this._calculateTime(this.startTime, this.endTime);
};

Timer.prototype.result = function() {
  return this.time;
};

Timer.prototype._calculateTime = function(startDate, endDate) {
  return endDate.getTime() - startDate.getTime();
};

Timer.prototype.toString = function() {
  if (this.time === undefined) {
    return "No timing data.";
  } else {
    if (this.running) {
      return "Timer is running '" + this.name + "', current time : " + this._calculateTime(this.startTime, this.endTime) + " ms.";
    } else {
      return "Timer '" + this.name + "' : " + this.time + " ms.";
    }
  }
};

var __everyOther = new Object();

function everyOther(sub) {
  if (!sub) sub = "default";
  return __everyOther[sub] = !__everyOther[sub];
}

/**
 * Counts how many charToCount-characters that are in the beginning of the string.
 * For example: "00123", "0" -> 2 .. There are 2 zeroes in the beginning.
 * For example "00123", "0" -> "123".
 * @param s
 * @param character
 */
function countInitChars(s, charToCount) {
  var i = 0;
  for (i = 0; i < s.length; i++) {
    if (s.charAt(i) != charToCount) break;
  }
  return i;
}

/**
 * If the string starts with n characters of type "character" (the parameter), these are removed.
 * For example "00123", "0" -> "123".
 * @param s
 * @param character
 */
function removeInitCharacters(s, character) {
  if (typeof s !== "string") {
    ibeerror("removeInitZeroes argument must be a string.");
    return s;
  }
  var zeroes = countInitChars(s, character);
  return s.substring(zeroes, s.length);
}

function removeInitZeroes(s) {
  return removeInitCharacters(s, "0");
}

/**
 * Calculates the sum of all numbers in a string. All characters in the string must be numbers.
 * @param s
 */
function sumOfSequence(s) {
  s = s.toString();
  if (!stringIsNumeric(s)) {
    ibeerror("String must be numeric.");
    return 0;
  } else {
    if (s.length == 1) return parseInt(s);
    var sum = 0;
    var max = s.length;
    for (var i = 0; i < max; i++) {
      sum += parseInt(s.charAt(i));
    }
    return sum;
  }
}

function validateSwedishSso(no) {
  // TODO: Allow "-" ? If so, just remove any "-" before next row.
  if (stringIsNumeric(no) && no.length == 10) {
    var lastNumber = parseInt(no.getLastCharacter());
    var multiplier = 2;
    var sum = 0;
    for (var i = 0; i < 9; i++) {
      sum += sumOfSequence(parseInt(no.charAt(i) * multiplier));
      multiplier = multiplier == 1 ? 2 : 1;
    }
    var controlNumber = 10 - parseInt(sum.toString().getLastCharacter());
    return controlNumber == lastNumber;
  } else {
    ibeerror("Swedish social security number passed to validateSso(..) most only contain numbers and be 10 characters (only numbers) long.");
    return false;
  }
}

function validateFinnishSso(no) {
  if (no && no.length == 11) {
    var controlArray = ["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f","h","j","k","l","m","n","p","r","s","t","u","v","w","x","y","ö"];
    var day = no.substring(0, 2);
    var month = no.substring(2, 4);
    var year = no.substring(4, 6);
    var b = no.substring(7, 10);
    if (day < 1 || day > 31) return false;
    if (month < 1 || month > 12) return false;
    var c = parseInt(removeInitZeroes(day + month + year + b)); // Must remove any init zeroes, otherwise parseInt will parse octal number instead of base 10.
    var control = c % 31;
    var controlCharacter = controlArray[control];
    return no.getLastCharacter().toLowerCase() == controlCharacter.toLowerCase();
  } else {
    ibeerror("Finnish social security number passed to validateSso(..) most be 11 characters long.");
    return false;
  }
}

function validateNorwegianSso(no) {
  if (no && no.length == 11 && stringIsNumeric(no)) {
    var day = no.substring(0, 2);
    var month = no.substring(2, 4);
    var year = no.substring(4, 6);
    if (day < 1 || day > 31) return false;
    if (month < 1 || month > 12) return false;
    var controlNumber1 = 11 - (((3 * no.charAt(0)) + (7 * no.charAt(1)) + (6 * no.charAt(2)) + (1 * no.charAt(3)) + (8 * no.charAt(4)) + (9 * no.charAt(5)) + (4 * no.charAt(6)) + (5 * no.charAt(7)) + (2 * no.charAt(8))) % 11);
    var controlNumber2 = 11 - (((5 * no.charAt(0)) + (4 * no.charAt(1)) + (3 * no.charAt(2)) + (2 * no.charAt(3)) + (7 * no.charAt(4)) + (6 * no.charAt(5)) + (5 * no.charAt(6)) + (4 * no.charAt(7)) + (3 * no.charAt(8)) + (2 * no.charAt(9))) % 11);
    return ((controlNumber1 == no.charAt(9)) || (controlNumber1 > 10)) && ((controlNumber2 == no.charAt(10)) || (controlNumber2 > 10));

  } else {
    ibeerror("Norwegian social security number passed to validateSso(..) most be 11 characters long and only numbers!");
    return false;
  }
}

function validateSso(no, language) {
  if (!no) {
    ibewarning("validateSso(..) did not get a language parameter. Defaulting to swedish.");
    language = 2;
  }
  if (!stringIsNumeric(language)) {
    switch (language.toLowerCase().trim()) {
      case "se":
      case "swe":
      case "swedish":
      case "sweden":
        language = 2;
        break;
      case "no":
      case "nor":
      case "norwegian":
      case "norway":
        language = 9;
        break;
      case "fi":
      case "fin":
      case "finnish":
      case "finland":
        language = 5;
        break;
      default:
        ibeerror("Running validateSso(..) with language parameter='" + language + "', but it could not be resolved.");
        return true;
    }
  }
  switch (language) {
    case 2:
      return validateSwedishSso(no);
      break;
    case 5:
      return validateFinnishSso(no);
      break;
    case 9:
      return validateNorwegianSso(no);
      break;
    default:
      ibeerror("Runnig validateSso(..) but specified language is not supported. Only finnish, swedish and norwegian is supported.");
      return true;
  }
}

/**
 * public static final long ENGLISH = 1L;
 public static final long SWEDISH = 2L;
 public static final long DANISH = 3L;
 public static final long GERMAN = 4L;
 public static final long FINNISH = 5L;
 public static final long FRENCH = 6L;
 public static final long ITALIAN = 7L;
 public static final long DUTCH = 8L;
 public static final long NORWEGIAN = 9L;
 public static final long SPANISH = 10L;
 public static final long POLISH = 11L;

 *
 */


function IbeEventManager() {

  this._eventListeners = new Array();

  this.signal = function(eventType, args) {
    args = $.extend({}, args);
    var listToRun = new Array();
    if (!eventType) {
      ibewarning("Tried to signal listeners, but no eventType is specified. Defaulting to 'all'.");
      eventType = "all";
    }
    if (typeof eventType !== "string") {
      ibewarning("Tried to signal listeners, but eventType is not a string. Defaulting to 'all'.");
      eventType = all;
    }
    // Make a copy of the list, since it might be modified by the listeners.
    for (var i = 0, l = this._eventListeners.length; i < l; i++) {
      var listener = this._eventListeners[i];
      if ((listener && listener.f && typeof listener.f === "function") &&
          (!eventType || !listener.eventType || listener.eventType.toUpperCase() === "ALL" ||
           eventType.toUpperCase() === listener.eventType.toUpperCase())) {
        // Run if listener has subscribed to this eventType, or if no eventType was specified, or if listener listens to all eventTypes.
        listToRun.push(listener.f);
      }
    }
    if (listToRun.length) {
      for (i = 0; i < listToRun.length; i++) {
        listToRun[i](args);
      }
    } else {
      ibenotice("Signaling event type '" + eventType + "', but there are no listeners.");
    }
  };

  /**
   * 999 executes before 55.
   * @param eventType
   * @param f
   * @param args
   */
  this.listen = function(eventType, f, args) {
    args = $.extend({
                      priority:0
                    }, args);
    if (!eventType) {
      ibewarning("Tried to add listener, but no eventType is specified. Defaulting to 'all'.");
      eventType = "all";
    }
    if (typeof eventType !== "string") {
      ibewarning("Tried to add listener, but eventType is not a string. Defaulting to 'all'.");
      eventType = all;
    }
    if (!this.hasEventListenerOnEventType(f, eventType)) {
      if (f && typeof f === "function") {
        this._eventListeners.push({
                                    f:f,
                                    priority: args.priority,
                                    eventType: eventType.toUpperCase()
                                  });
      }
      this._sortEventListeners();
    } else {
      ibewarning("Tried to add listener to event '" + eventType + "', but that listener is already subscribed to that event.");
    }
  };

  this._sortEventListeners = function() {
    this._eventListeners = IBESorter._insertionSort(this._eventListeners, "priority", true);
  };

  this.hasEventListener = function(f) {
    for (var i = 0, l = this._eventListeners.length; i < l; i++) {
      if (this._eventListeners[i].f == f) return true;
    }
    return false;
  };

  this.hasEventListenerOnEventType = function(f, eventType) {
    for (var i = 0, l = this._eventListeners.length; i < l; i++) {
      var listener = this._eventListeners[i];
      if (listener.f == f && listener.eventType == eventType) return true;
    }
    return false;
  };

  this.removeEventListener = function(f) {
    for (var i = 0, l = this._eventListeners.length; i < l; i++) {
      if (this._eventListeners[i].f == f) {
        this._eventListeners.splice(i, 1);
      }
    }
  };

  this.removeAllEventListeners = function() {
    this._eventListeners = new Array();
  };

  this.debugPrintEventListener = function(f) {
    for (var i = 0; i < this._eventListeners.length; i++) {
      ibedebug(this._eventListeners[i]);
    }
  };

}
var IBEPopupWindow = (function() {
  var popupWindowProcessManager, popupWindowContainers = {};

  function createIframe(suffix, url) {
    var iframe = document.createElement('iframe');
    iframe.src = url;
    iframe.id = 'iframe_' + suffix;
    iframe.frameBorder = 0;
    iframe.style.border = 0;
    iframe.style.width = '500px';
    iframe.style.height = '500px';
    return iframe;
  }

  function isContainerCreated(popupWindowContainerId) {
    if (popupWindowContainers[popupWindowContainerId]) {
      return true;
    }
    // set container id to be created
    popupWindowContainers[popupWindowContainerId] = popupWindowContainerId;
    return false;
  }

  /**
   * This function binds close events after the new content has been rendered in the container. The event can only be
   * triggered once.
   *
   * @param $overlay the jquery overlay to close.
   * @param options this is the config object.
   */
  function attachCloseEvents($overlay, options) {
    // Close overlay when clicking outside. This event is bound when the ajax request
    // has returned.
    if (options.closeOnClickOutside) {
      $(document).one('click', function() {
        $overlay.close();
      });
    }
    // Close when the close button is clicked
    $("#" + options.container + " .close").one('click', function() {
      $overlay.close();
    });
  }

  return {
    /**
     * Popup window function - only supports ajax response is of dataType HTML.
     *
     * Usage: IBEPopupWindow.show($triggerElement, {url:'http://....'});
     *
     * @param $el that the overlay should be attached on, e.g. the trigger element for the overlay.
     * @param options is an object containing some default options to override, or can be undefined then the default
     * values will be used.
     */
    show : function($el, options) {
      var defaults, overlay;
      // Instantiate process manager used in popup window method.
      if (!popupWindowProcessManager) popupWindowProcessManager = new IBEProcessManager("pushover");
      // Some default properties that can be overridden
      defaults = {
        container : 'ibePopupWindowContainer',
        cssClass : 'simpleOverlay ibePopupWindowContainer',
        loadingAnimation : true,
        iframe : false,
        closeOnClickOutside : true
      };
      // extend default properties
      options = $.extend({}, defaults, options);

      if (!options.url) throw "No url defined.";
      if (!options.container) throw "No container defined.";

      if (!isContainerCreated(options.container)) {
        $("body").append('<div id="' + options.container + '" class="' + options.cssClass + '"></div>');
      }

      overlay = $el.data('overlay');
      if (overlay) {
        overlay.load(); // An overlay is already attached to this element -- display it!
      } else {
        // initialize overlay for this element
        $el.overlay({
                      target : '#' + options.container, // the overlay content goes here.
                      effect: 'default',
                      api : true,
                      load: true,
                      onBeforeLoad : function() {
                        var $this = this;
                        if (options.iframe) {
                          var content = createIframe(options.container, options.url);
                          $("#" + options.container).html('<div class="close"></div>').append(content);
                          attachCloseEvents($this, options);
                        } else {
                          // When overlay is visible display loading animation
                          if (options.loadingAnimation) {
                            setInnerHtmlToLoadingAnimation(options.container);
                          }
                          // Fetch content via ajax and place response in the container
                          $.ajax({
                                   url : options.url,
                                   dataType : 'html',
                                   success: function(response, textStatus) {
                                     $("#" + options.container).html('<div class="close"></div>').append(response);
                                     attachCloseEvents($this, options);
                                   },
                                   error: function(request, textStatus, error) {
                                     $("#" + options.container).html('');
                                     $this.close(); // If an error occurs - close overlay
                                   }
                                 });
                        }
                      }
                    });
      }
    } // end show()

  };

})();

/*****
 Argument validation
 *****/


function validateArgumentList(variableList, config, variableNameList) {
  var hasNameList = variableNameList && variableNameList.length;
  if (variableList && variableList.length) {
    if (!$.isArray(variableList)) {
      ibeerror("Specified variable list is not an array.");
      return;
    }
    for (var i = 0; i < variableList.length; i++) {
      validateArgument(variableList[i], config, hasNameList ? variableNameList[i] : undefined);
    }
  }
}

/**
 *
 * @param variable
 * @param config
 * @param variableName
 */
function validateArgument(variable, config, variableName) {

  config = extendValidationConfig(config);

  this.validationFailed = function(message) {
    //ibelog(message, variable, config);
    ibeerror(message);
  };

  if (!variableName) variableName = "unknown name";

  if (config.rules.allowUndefined === false && variable === undefined) {
    this.validationFailed(insertArgument(config.messages.isUndefined, variableName));
  } else if (config.rules.allowUndefined === true && variable === undefined) {
    // If it is undefined, and we allow it, no more tests are needed.
    return;
  }

  if (config.rules.allowTypes && (config.rules.allowTypes.length && !config.rules.allowTypes.contains(typeof variable))) {
    this.validationFailed(insertArgument(config.messages.isWrongType, variableName, typeof variableName, config.rules.allowTypes));
  }
  if (config.rules.requireValueIsOneOf && (config.rules.requireValueIsOneOf.length && !config.rules.requireValueIsOneOf.contains(variable))) {
    this.validationFailed(insertArgument(config.messages.isWrongValue, variableName, variable, config.rules.requireValueIsOneOf));
  }
  if (config.rules.requireString) {
    if (typeof variable === "string") {} // OK
    else {
      this.validationFailed(insertArgument(config.messages.isNotString, variableName, variable));
    }
  }
  if (config.rules.requireStringOrNumber) {
    if (typeof variable === "string") {} // OK
    else if (typeof variable === "number") {} // OK
    else {
      this.validationFailed(insertArgument(config.messages.isNotString, variableName, variable));
    }
  }
  if (config.rules.requireNumber) {
    if (typeof variable === "number") {} // OK
    else {
      this.validationFailed(insertArgument(config.messages.isNotNumber, variableName, variable));
    }
  }
  if (config.rules.requireObject) {
    if (typeof variable === "object") {} // OK
    else {
      this.validationFailed(insertArgument(config.messages.isNotObject, variableName, variable));
    }
  }
  if (config.rules.requireFunction) {
    if (typeof variable === "function") {} // OK
    else {
      this.validationFailed(insertArgument(config.messages.isNotFunction, variableName, variable));
    }
  }

  // Boolean

  if (config.rules.requireBoolean) {
    if (variable === true || variable === false || (config.rules.allowUndefined && variable === undefined)) {
      // OK
    } else {
      this.validationFailed(insertArgument(config.messages.isNotBoolean, variableName, variable));
    }
  }

  // Check all types of lists.

  if (config.rules.requireList || config.rules.requireListOfNumbers || config.rules.requireListOfStrings) {
    if (config.rules.allowUndefined && variable === undefined) {
      // OK
    } else {
      if (!$.isArray(variable)) {
        this.validationFailed(insertArgument(config.messages.isNotList, variableName));
      } else {
        if (!variable.length) {
          if (config.rules.requireElementsInList) {
            this.validationFailed(insertArgument(config.messages.listHasNoElements, variableName));
          }
        } else {
          if (config.rules.requireListOfStrings && !variable.containsOnlyStrings()) {
            this.validationFailed(insertArgument(config.messages.isNotListOfStrings, variableName));
          }
          if (config.rules.requireListOfNumbers && !variable.containsOnlyNumbers()) {
            this.validationFailed(insertArgument(config.messages.isNotListOfNumbers, variableName));
          }
        }
      }
    }
  }

  if (config.rules.requireIdExistsInDom) {
    if (!document.getElementById(variable)) {
      this.validationFailed(insertArgument(config.messages.idDoesNotExistInDom, variable));
    }
  }

  if (config.rules.requireDomElement) {
    if (!variable.nodeType) {
      this.validationFailed(insertArgument(config.messages.isNotDomElement, variable));
    }
  }

}

function extendValidationConfig(config) {

  config = $.extend({
                      rules: {
                        allowUndefined:false, // undefined, true, false

                        allowTypes:undefined, // ["string", "number"]

                        requireValueIsOneOf:undefined, // ["mattias", "is", 5]

                        requireBoolean:false,
                        requireStringOrNumber:false,
                        requireString:false,
                        requireNumber:false,
                        requireObject:false,
                        requireFunction:false,
                        requireList:false,
                        requireListOfNumbers:false,
                        requireListOfStrings:false,
                        requireElementsInList:false,
                        requireIdExistsInDom:false,
                        requireDomElement:false
                      },
                      messages:{
                        isUndefined:"Argument '{0}' is undefined, which is not allowed.",
                        isWrongType:"Argument '{0}' is of wrong type: {1}. Allowed types are: {2}.",

                        isWrongValue:"Argument '{0}' is of wrong value: {1}. Allowed values are: {2}.",

                        isNotBoolean:"Argument '{0}' must be a boolean, value is: {1}.",
                        isNotString:"Argument '{0}' must be a string, value is: {1}.",
                        isNotStringOrNumber:"Argument '{0}' must be a string or a number, value is: {1}.",
                        isNotNumber:"Argument '{0}' must be a number, value is: {1}.",
                        isNotObject:"Argument '{0}' must be an object, value is: {1}.",
                        isNotFunction:"Argument '{0}' must be a function, value is: {1}.",

                        isNotList:"Argument '{0}' must be a list.",
                        isNotListOfStrings:"Argument '{0}' must be a list of strings.",
                        isNotListOfNumbers:"Argument '{0}' must be a list of numbers.",
                        listHasNoElements:"Argument '{0}' must be a list with elements, it is empty.",

                        idDoesNotExistInDom:"Argument '{0}' must be the id of a DOM element, but it is not.",
                        isNotDomElement:"Argumenet '{0}' must be a DOM element, it is not."
                      }
                    }, config);
  return config;
}

function validateArgumentValidationConfig(config) {
  config = extendValidationConfig(config);

  validateArgument(config.rules.allowUndefined, {rules:{allowUndefined:true, requireBoolean:true}}, "config.rules.allowUndefined");

  validateArgument(config.rules.allowTypes, {rules:{allowUndefined:true, requireListOfStrings:true}}, "config.rules.allowTypes");
  validateArgument(config.rules.requireValueIsOneOf, {rules:{allowUndefined:true, requireList:true}}, "config.rules.requireValueIsOneOf");

  validateArgument(config.rules.requireBoolean, {rules:{allowUndefined:true, requireBoolean:true}}, "config.rules.requireBoolean");
  validateArgument(config.rules.requireString, {rules:{allowUndefined:true, requireBoolean:true}}, "config.rules.requireString");
  validateArgument(config.rules.requireStringOrNumber, {rules:{allowUndefined:true, requireBoolean:true}}, "config.rules.requireString");
  validateArgument(config.rules.requireNumber, {rules:{allowUndefined:true, requireBoolean:true}}, "config.rules.requireNumber");
  validateArgument(config.rules.requireObject, {rules:{allowUndefined:true, requireBoolean:true}}, "config.rules.requireObject");
  validateArgument(config.rules.requireFunction, {rules:{allowUndefined:true, requireBoolean:true}}, "config.rules.requireFunction");

  validateArgument(config.rules.requireList, {rules:{allowUndefined:true, requireBoolean:true}}, "config.rules.requireList");
  validateArgument(config.rules.requireListOfNumbers, {rules:{allowUndefined:true, requireBoolean:true}}, "config.rules.requireListOfNumbers");
  validateArgument(config.rules.requireListOfStrings, {rules:{allowUndefined:true, requireBoolean:true}}, "config.rules.requireListOfStrings");

  validateArgument(config.rules.requireIdExistsInDom, {rules:{allowUndefined:true, requireBoolean:true}}, "config.rules.requireIdExistsInDom");
  validateArgument(config.rules.requireDomElement, {rules:{allowUndefined:true, requireBoolean:true}}, "config.rules.requireDomElement");

  // Messages

  validateArgument(config.messages.isUndefined, {rules:{allowUndefined:false, requireString:true}}, "config.messages.isUndefined");
  validateArgument(config.messages.isWrongType, {rules:{allowUndefined:false, requireString:true}}, "config.messages.isWrongType");

  validateArgument(config.messages.isWrongValue, {rules:{allowUndefined:false, requireString:true}}, "config.messages.isWrongValue");

  validateArgument(config.messages.isNotBoolean, {rules:{allowUndefined:false, requireString:true}}, "config.messages.isNotBoolean");
  validateArgument(config.messages.isNotString, {rules:{allowUndefined:false, requireString:true}}, "config.messages.isNotString");
  validateArgument(config.messages.isNotStringOrNumber, {rules:{allowUndefined:false, requireString:true}}, "config.messages.isNotStringOrNumber");
  validateArgument(config.messages.isNotNumber, {rules:{allowUndefined:false, requireString:true}}, "config.messages.isNotNumber");
  validateArgument(config.messages.isNotObject, {rules:{allowUndefined:false, requireString:true}}, "config.messages.isNotObject");
  validateArgument(config.messages.isNotFunction, {rules:{allowUndefined:false, requireString:true}}, "config.messages.isNotFunction");

  validateArgument(config.messages.isNotList, {rules:{allowUndefined:false, requireString:true}}, "config.messages.isNotList");
  validateArgument(config.messages.isNotListOfStrings, {rules:{allowUndefined:false, requireString:true}}, "config.messages.isNotListOfStrings");
  validateArgument(config.messages.isNotListOfNumbers, {rules:{allowUndefined:false, requireString:true}}, "config.messages.isNotListOfNumbers");
  validateArgument(config.messages.listHasNoElements, {rules:{allowUndefined:false, requireString:true}}, "config.messages.listHasNoElements");

  validateArgument(config.messages.idDoesNotExistInDom, {rules:{allowUndefined:false, requireString:true}}, "config.messages.idDoesNotExistInDom");
  validateArgument(config.messages.isNotDomElement, {rules:{allowUndefined:false, requireString:true}}, "config.messages.isNotDomElement");
}


