/**
  * @file    util.js
  * @desc    JavaScript utilities for Eogs - ReviReg.
  *          <p>
  *          The functions can be used or overwritten in the <tt>page-script</tt>
  *          XSLT template defined for each page view.
  *          <p>
  *          Terminology: <ul>
  *     <li> <i>Page</i>: a page in the application as defined in
  *          the Java code.
  *     <li> <i>Page id</i>: the id of a page as defined in the Java
  *          code and the XML configuration.
  *     <li> <i>Form</i>: a HTML form.
  *     <li> <i>Form Action</i>: to which page is the form submitted?
  *          Is actually a page id.
  *     <li> <i>Command</i>: form button to submit the form with a
  *          specific action (to a given page represented by a page id).
  *     <li> <i>Command id</i>: the id of the command. Is the
  *          <tt>id</tt> of the corresponding HTML element. All command
  *          ids are prefixed with <tt>command_</tt>, but need not
  *          be when invoking most function since the ids are resolved.
  *     <li> <i>Element</i>: HTML element such as INPUT, SELECT, etc.
  *     <li> <i>Element id</i>: the id of the element. Is the
  *          <tt>id</tt> of the corresponding HTML element.
  *     <li> <i>Navigator</i>: large pages can be split up into smaller
  *          chunks, and a navigator controls the navigation between
  *          such page chunks.
  *          </ul>
  * @author  Gunni Rode <gunni.rode@tietoenator.com>
  * @company TietoEnator <http://www.tietoenator.com>
  * @date    2004
  */

// Browser detection...
function BrowserDetect() {
  this.av             = navigator.appVersion.toLowerCase();
  this.browserName    = navigator.appName.toLowerCase();
  this.browserVersion = parseInt(this.av);

  this.dom = (document.getElementById) ? true : false;
  this.ie  = ((this.dom) && ((this.av.indexOf('msie 5') >= 0) || (this.av.indexOf('msie 6') >= 0) || (this.av.indexOf('msie 7') >= 0) || (this.av.indexOf('msie 8') >= 0)));
  this.op  = ((this.dom) && (this.browserName.indexOf('opera') >= 0) && (this.browserVersion >= 7));
  this.ns  = ((this.dom) && (this.browserName.indexOf('netscape') >= 0) && (this.browserVersion >= 5));

  this.win  = (this.av.indexOf('win') >= 0) ? 1 : 0;
  this.unix = (this.av.indexOf('x11') >= 0) ? 1 : 0;
  this.mac  = (this.av.indexOf('mac') >= 0) ? 1 : 0;

  if (!(this.ie || this.op || this.ns)) {
    showError("Din browser er desværre ikke understøttet af ReviReg!");
  }
}

// True if internal JavaScript on page...
var hasError = false;
// True if a validation error has been alerted to the user...
var notified = false;
// True if return has been pressed...
var returnPressed = false;
// Hashmap of open windows...
var windows = {};

/**
  * @section Global variables
  * @desc    Global variables storing the page state.
  *
  * @const   initialised True after all elements have been initialised.
  *                      Since handlers can be invoked while initialising, they
  *                      can check on this value to distinquish between a
  *                      handler invocation in conjunction with initialisation or
  *                      a "pure" handler invocation.
  * @const   commands Hash map of all commands (buttons) in the current page.
  * @const   fields   Hash map of all fields, i.e., input fields such as INPUT,
  *                   SELECT, etc.
  * @const   browser  Browser check object identifying the browser used by the
  *                   client: <ul>
  *              <li> <tt>browser.ie == true</tt> --> Internet Explorer
  *              <li> <tt>browser.op == true</tt> --> Opera
  *              <li> <tt>browser.ns == true</tt> --> Netscape
  *                   </ul>
  */
var initialised = false;
var commands = {};
var fields = {};
var browser = new BrowserDetect();

// Private page state...
var activatedCommand = null;
var busy = false;
var deac = true;

// Saves the previous value of an input field...
function saveValue(id) {
  var e = resolveElement(id);
  validate(e.id);
  function save(element) {
    fields[element.id].value = element.value;
  }
  save(e);
  // Next time, don't bother validating...
  e.onkeydown = function(){save(e)};
}

// Update returnPressed as needed...
function handleReturn() {
  var evt = (browser.ie) ? window.event : arguments[0];
  if (returnPressed = ((evt) && (evt.keyCode == 13))) {
    setCommand(getDefaultCommand());
    var element = (browser.ie) ? evt.srcElement : evt.target;
    returnActivatedHandler(element);
  }
}

// Handler to overwrite...
function returnActivatedHandler(element) {
}

/**
  * @function clearBusy
  * @desc     Clears the <i>busy flag</i> for the page. A page is
  *           busy when a command has been activated and as long as it
  *           is busy, no other command can be activated.
  *           <p>
  *           This function should be invoked when a pre-submit check
  *           fails; if not, commands cannot be activated again.
  *           However, if <tt>commandActivatedHandler</tt> returns
  *           false, i.e., do not submit, this function is automatically
  *           invoked.
  * @comment  The <tt>deactivate</tt> parameter is only valid if
  *           invoked from <tt>commandActivatedHandler</tt>; otherwise
  *           ignored.
  *
  * @param    deactivate If true, deactivate all commands when the
  *                      page is unloaded. If false, do not deactivate
  *                      commands on unload. If not supplied the
  *                      commands will be deactivated.
  * @see      commandActivatedHandler
  */
function clearBusy(deactivate) {
  busy = false;
  deac = (deactivate == undefined) ? true : deactivate;
  activatedCommand = null;
}

// Invoked when a command has been activated...
function commandActivated(commandId, deactivate, commandValue) {
  deac = true;
  var id = (returnPressed) ? getDefaultCommand() : commandId;
  if (!(busy = (activatedCommand != null))) {
    setCommand(activatedCommand = resolveCommandId(id), commandValue);
    var res = commandActivatedHandler(activatedCommand);
    if (!res) {
      clearBusy();
    } else if (deactivate != undefined) {
      clearBusy(deactivate);

    // Check button type: If image, we must submit ourselves...
    } else if (res) {
      // Use id since activateCommand could have been cleared...
      if (getCommandTypes(id)[0] == 'image') {
        formSubmit(true);
      }
    }
    return res;
  }
  return false;
}

// Wrapper to submit a commad via the form from a link...
function submitCommand(commandId, commandValue, deactivate) {
  if (commandActivated(commandId, deactivate, commandValue)) {
    formSubmit(true);
  }
  return false;
}

// Invoked when a navigator has been activated...
function navigatorActivated(navigatorId, index, params) {
  if (busy) {
    return false;
  }
  busy = true;
  deac = true;
  var res = navigatorActivatedHandler(navigatorId, index, params);
  if (res) {
    setNavigator(navigatorId, index);
  } else {
    clearBusy();
  }
  return false;
}

/**
  * @function commandActivatedHandler
  * @desc     Handler invoked when a command has been activated. The
  *           command is identified by its fully qualified name
  *           supplied as <tt>commandId</tt>. The handler must be
  *           able to handle - or ignore - all commands in a given
  *           page.
  *           <p>
  *           If this handler return true, the form <b>will</b> be
  *           submitted. If false is returned, the form <b>will not</b>
  *           be submitted and the busy flag is cleared for the page.
  *           Also, if true is returned no other command can be
  *           activated, while the busy flag is cleared if false is
  *           returned.
  *           <p>
  *           The default implementation does nothing but return true.
  *           Should be <b>overwritten</b> in a given page to implement
  *           the desired functionality.
  *
  * @param    commandId Fully qualified command id, i.e., prefixed
  *                     <tt>command_</tt>; never null.
  * @return   True to submit, false to not submit.
  * @see      commandInactiveHandler
  * @example  Overwrite this function to promt the user for confirmation before
  *           submitting the page; the qualified command id is
  *           <tt>command_save</tt>. If the user user cancels, this function
  *           will return false and the form submit is cancelled as well.
  *           <pre>
  *              function commandActivatedHandler(commandId) {   <br>
  * &nbsp;         if (commandId == 'command_save') {            <br>
  * &nbsp; &nbsp;    return confirm('Save?');                    <br>
  * &nbsp;         }                                             <br>
  * &nbsp;         // Another command activated...               <br>
  * &nbsp;         return true;                                  <br>
  *              }
  *           </pre>
  */
function commandActivatedHandler(commandId) {
  return true;
}

// Invoked when an inactive command is clicked (tried activated)...
function commandInactive(commandId) {
  commandInactiveHandler(resolveCommandId(commandId));
  return false;
}

/**
  * @function commandInactiveHandler
  * @desc     Handler invoked when an <b>inactive</b> command is tried
  *           activated. An inactive command will never result in the
  *           form being submitted.
  *           <p>
  *           A typical use of this handler is to alert a given message
  *           to the user.
  *           <p>
  *           The default implementation does nothing. Should be
  *           <b>overwritten</b> in a given page to implement the
  *           desired functionality.
  *
  * @param    commandId Fully qualified command id, i.e., prefixed
  *                     <tt>command_</tt>; never null.
  * @see      commandActiveHandler
  * @example  Overwrite this function to alert the user that the command
  *           with id <tt>command_save</tt> cannot be activated:
  *           <pre>
  *              function commandInactiveHandler(commandId) {    <br>
  * &nbsp;         if (commandId == 'command_save') {            <br>
  * &nbsp; &nbsp;    alert('Not active!');                       <br>
  * &nbsp;         }                                             <br>
  *              }
  *           </pre>
  */
function commandInactiveHandler(commandId) {
}

/**
  * @function navigatorActivatedHandler
  * @desc     Handler invoked when a navigator has been activated. The
  *           navigator is identified by its fully qualified name
  *           supplied as <tt>navigatorId</tt>. The handler must be
  *           able to handle - or ignore - all navigators in a given
  *           page.
  *           <p>
  *           If this handler return true, the navigator <b>will</b> be
  *           activated. If false is returned, the navigator <b>will not</b>
  *           be activated (submitted) and the busy flag is cleared for
  *           the page. Also, if true is returned no other navigator can be
  *           activated, while the busy flag is cleared if false is
  *           returned.
  *           <p>
  *           The default implementation does nothing but return true.
  *           Should be <b>overwritten</b> in a given page to implement
  *           the desired functionality.
  *
  * @param    navigatorId Fully qualified navigator id, i.e., prefixed
  *                       <tt>nav_</tt>; never null.
  * @param    index       Page index.
  * @param    params      Additional parameters, if any.
  * @return   True to activate, false to not activate.
  * @example  Overwrite this function to promt the user for confirmation before
  *           activating the navigator; the qualified navigator id is
  *           <tt>nav_navigator</tt>. If the user user cancels, this function
  *           will return false and the navigator does nothing.
  *           <pre>
  *              function navigatorActivatedHandler(navigatorId, index) { <br>
  * &nbsp;         if (navigatorId == 'nav_navigator') {                  <br>
  * &nbsp; &nbsp;    return confirm('Goto page ' + (index + 1) + '?');    <br>
  * &nbsp;         }                                                      <br>
  * &nbsp;         // Another navigator activated...                      <br>
  * &nbsp;         return true;                                           <br>
  *              }
  *           </pre>
  */
function navigatorActivatedHandler(navigatorId, index, params) {
  return true;
}

/**
  * @function getForm
  * @desc     Returns the document form with the index supplied as
  *           <tt>index</tt>.
  *
  * @param    index The form index; default value is zero.
  * @return   The form with the index supplied as <tt>index</tt>.
  * @throws   Error If no such form exist.
  */
function getForm(index) {
  var i = (index != undefined) ? index : 0;
  if (document.forms[i]) {
    return document.forms[i];
  }
  throw new Error("Document contains no form with index: " + i);
}

/**
  * @function formSubmit
  * @desc     Submits the form with the form index supplied as
  *           <tt>index</tt>; if null, zero is used for the index.
  *           <p>
  *           The submit method preservers form anchor context by storing
  *           the anchor value in the hidden input field named
  *           <tt>form + "_anchor"</tt>, if present.
  *
  * @param    validate True to validate form, false for not.
  * @param    index The form index; if null, zero is used.
  */
function formSubmit(validate, index) {
  if (!validate || check()) {
    getForm(index).submit();
  }
}

/**
  * @function setFormAnchor
  * @desc     Sets the anchor value to <tt>anchor</tt> for the
  *           form with the index supplied as <tt>index</tt>.
  *           <p>
  *           For the anchor to be activated, <tt>setFocus</tt>
  *           must be called on load with the anchor value.
  *
  * @param    anchor The anchor name; cannot be null.
  * @param    index  The form index; default is zero.
  * @see      setFocus
  */
function setFormAnchor(anchor, index) {
  var f = getForm(index);
  var element = getHTMLElement(f.id + "_anchor");
  if (element) {
    element.value = anchor;
  }
}

/**
  * @function setFocus
  * @desc     Brings the HTML element with the id supplied as <tt>id</tt>
  *           into focus.
  *           <p>
  *           This function can be called at any time prior to the
  *           page has fully loaded; the focus will first be set after
  *           the page has completed loading.
  *
  * @param    id The element id; can be null, in which case this method
  *              does nothing.
  */
function setFocus(id) {
  if (id) {
    addHandler("onload", function() {
                           var elements = document.getElementsByName(id);
                           if (elements.length) {
                             var e = elements[0];
                             if (e.tagName == 'A') {
                               redirect('#' + id);
                             } else {
                               try {
                                 e.focus();
                                 e.select();
                               } catch (e) {
                               }
                             }
                           }
                         });
  }
}

/**
  * @function valueChangedHandler
  * @desc     Handler invoked when an element has changed its
  *           value. The element is passed as the parameter
  *           <tt>element</tt>.
  *           <p>
  *           If this handler return true, the changed is accepted, not
  *           if false.
  *           <p>
  *           The default implementation does nothing but return true.
  *           Should be <b>overwritten</b> in a given page to implement
  *           the desired functionality.
  *
  * @param    element The select element; never null.
  * @return   True for accept, false for decline.
  */
function valueChangedHandler(element) {
  return true;
}

// Internal handler invoked when a select element has changed its
// value...
function selectChanged(elementOrId) {
  return valueChangedHandler(resolveElement(elementOrId));
}

// Internal handler invoked when an input element has changed its
// value...
function inputChanged(elementOrId) {
  var e = resolveElement(elementOrId);
  if (internalHasValueChanged(e)) {
    return valueChangedHandler(e);
  }
  return true;
}

/**
  * @function selectChangedDisplay
  * @desc     Displays the selected option, if any, as text below the
  *           select element. If the user clicks on the text, it will
  *           be hidden again.
  *           <p>
  *           Should normally be invoked from
  *           <tt>valueChangedHandler</tt>.
  *
  * @param    elementOrId Element or element id; cannot be null.
  * @param    elementId   Element id to match if <tt>elementOrId</tt>
  *                       is an element; can be null.
  * @see      valueChangedHandler
  * @example  Use this function to display the selected option for the
  *           SELECT element with id <tt>foo</tt>; we activate it from
  *           the <tt>valueChangedHandler</tt>:
  *           <pre>
  *              function valueChangedHandler(element) {        <br>
  * &nbsp;         if (element.name == 'foo') {                 <br>
  * &nbsp; &nbsp;    selectChangedDisplay(element);             <br>
  * &nbsp;         }                                            <br>
  * &nbsp;         return true;                                 <br>
  *              }
  *           </pre>
  */
function selectChangedDisplay(elementOrId, elementId) {
  if (!browser.ie) {
    return;
  }
  var e = resolveElement(elementOrId);
  if (!e) {
    return;
  }
  if ((elementId != null) && (e.name != elementId)) {
    return;
  }
  var div = getHTMLElement(e.name + "_display_text");
  if (div) {
    if ((e.selectedIndex != -1) && (e.options[e.selectedIndex].value)) {
      div.innerText = e.options[e.selectedIndex].text; // FIXME!
      var tr = getHTMLElement(e.name + "_display");
      if (!tr.onclick) {
        addHandler(tr, "onclick", function() {hide(tr); return false;});
      }
      show(e.name + "_display");
    } else {
      hide(e.name + "_display");
    }
  }
}

// Value changed since last onkeydown event?
function internalHasValueChanged(elementOrId) {
  var e = resolveElement(elementOrId);
  var field = fields[e.id];
  // If no field is registered, we cannot determine anything...
  if (field) {
    return (resolveValue(field, e.value) != resolveValue(field, field.value));
  }
  return true;
}

/**
  * @function hasValueChanged
  * @desc     Returns true if the element value has changed from the
  *           value it had at load time.
  *
  * @param    elementOrId Element or element id; cannot be null.
  * @return   True if changed, false if not.
  */
function hasValueChanged(elementOrId) {
  var e = resolveElement(elementOrId);
  var field = fields[e.id];
  // If no field is registered, we cannot determine anything...
  if (field) {
    return (resolveValue(field, e.value) != resolveValue(field, field.onloadValue));
  }
  return true;
}

/**
  * @function isEmpty
  * @desc     Return true if the element represented by <tt>elementOrId</tt>
  *           has an <i>empty</i> value, false if not. An empty value
  *           is defined as either a value consisting of white space
  *           only or equal to the initial value of the element (ignoring
  *           white space) defined at page load time.
  *           <p>
  *           It can only test for initial values if the element
  *           is checked; otherwise the element is only considered empty
  *           if the current value does not contain at least one
  *           non-whitespace character.
  *
  * @param    elementOrId Element or element id; cannot be null.
  * @return   True if the element value is empty, not if false.
  */
function isEmpty(elementOrId) {
  var e = resolveElement(elementOrId);
  var field = fields[e.id];
  if (field) {
    var v = resolveValue(field, e.value);
    if (v) {
      return (v == resolveValue(field, field.checkValue));
    }

  // Can only check for empty value, ie. no defaults...
  } else {
    if ((e.value != null) && (e.value.toString().trim())) {
      return false;
    }
  }

  return true;
}

var elementPrefix = null;
var elementCache  = null;

// Return form elements...
function getElements(idPrefix) {
  if ((elementPrefix != null) && (elementPrefix == idPrefix) && (elementCache.size > 0)) {
    return elementCache;
  }
  var res      = arguments[1];
  var elements = (res != null) ? res.e : {};
  var f = getForm();
  var j = 0;
  for (var i = 0; i < f.elements.length; i++) {
    var e = f.elements[i];
    if (e.name.substr(0, idPrefix.length) == idPrefix) {
      elements[e.name] = {e: e,
                          f: fields[e.name],
                          i: j++};
    }
  }
  elementPrefix = idPrefix;
  return elementCache = {e: elements, size: (res == null) ? j : res.size + j};
}

/**
  * @function toggleCommand
  * @desc     Deactivates the command with the id <tt>commandId</tt>
  *           if the element represented by <tt>elementOrId</tt>
  *           is empty. If <tt>emptyOnly</tt> is false or not supplied,
  *           the command is also deactivated if the element value has
  *           not changed.
  *           <p>
  *           This function should be invoked from the
  *           <tt>valueChangedHandler</tt>. No element
  *           checks are needed in the handler function; that is,
  *           simply pass the relevant element and ids to this
  *           function, which then performs the check. If the resolved
  *           element represented by <tt>elementOrId</tt>
  *           has the id <tt>elementId</tt> the value is tested. If empty
  *           or unchanged, the command is deactivated. Otherwise the
  *           command is activated.
  *
  * @param    elementId   Element id, array of element ids, or id prefix;
  *                       cannot be null.
  * @param    commandId   Command id or array of command ids; cannot be null.
  * @param    elementOrId Element or id to test if changed; can be null.
  * @param    emptyOnly   True for only deactivating the command if
  *                       the value is empty. Default false. Can be an
  *                       array of true and false values corresponding
  *                       to each elements to check, i.e., must have same
  *                       array length as <tt>elementId</tt>.
  * @param    additionalCheck A boolean value which must be true if supplied for the
                          command to be toggled (to active).
  * @see      activate
  * @see      deactivate
  * @see      isEmpty
  */
function toggleCommand(elementId, commandId, elementOrId, emptyOnly, additionalCheck) {
  var active = false;
  // Single element id...
  if (elementOrId != null) {
    var e = resolveElement(elementOrId);
    if (e) {
      if (e.name == elementId) {
        if ((isEmpty(e)) || (!emptyOnly && !hasValueChanged(e))) {
          deactivate(commandId);
        } else {
          activate(commandId);
          active = true;
        }
      }
    }

  // Several ids...
  } else {
    if ((additionalCheck == undefined) || (additionalCheck)) {
      var elements;
      var empty = [];
      if (typeof elementId == 'string') {
        elements = getElements(elementId);
        if (emptyOnly != undefined) {
          if (typeof emptyOnly == 'boolean') {
            empty[elementId] = emptyOnly;
          } else {
            empty[elementId] = emptyOnly[0];
          }
        }
      } else {
        for (var i = 0; i < elementId.length; i++) {
          elements = getElements(elementId[i], elements);
          if (emptyOnly != undefined) {
            if (typeof emptyOnly == 'boolean') {
              empty[elementId[i]] = emptyOnly;
            } else {
              empty[elementId[i]] = emptyOnly[i];
            }
          }
        }
      }
      for (var i in elements.e) {
        var e = elements.e[i];
        if (e.f) {
          d = ((isEmpty(e.e)) || (!empty[i] && !hasValueChanged(e.e)));
        } else {
          d = getValue(e.e) ? false : true;
        }
        if (d) {
          active = false;
          break;
        } else {
          active = true;
        }
      }
    }
    if (active) {
      activate(commandId);
    } else {
      deactivate(commandId);
    }
  }
  return active;
}

/**
  * @function resolveElement
  * @desc     Returns the element represented by <tt>elementOrId</tt>.
  *           <p>
  *           If <tt>elementOrId</tt> is an element, it is simply
  *           returned. If <tt>elementOrId</tt> is a string (id), the
  *           element is looked up using the id. If no such element
  *           exists, null will be returned.
  *
  * @param    elementOrId Element or element id; cannot be null.
  * @return   The resolved element or null if not found.
  * @see      resolveCommand
  */
function resolveElement(elementOrId) {
  var e = elementOrId;
  if (typeof(elementOrId) == 'string') {
    e = getHTMLElement(elementOrId);
  }
  return e;
}

/**
  * @function resolveCommand
  * @desc     Returns the command represented by <tt>commandOrId</tt>.
  *           <p>
  *           If <tt>commandOrId</tt> is a command, it is simply
  *           returned. If <tt>commandOrId</tt> is a string (id), the
  *           command is looked up using the resolved id. If no such
  *           command exists, null will be returned.
  *           <p>
  *           The command id need not be fully qualified; it is resolved
  *           before lookup if needed.
  *
  * @param    commandOrId Command or command id; cannot be null. If
  *                       an id it need not be fully qualified (prefixed
  *                       <tt>command_</tt>, since it is resolved before
  *                       the lookup.
  * @return   The resolved command or null if not found.
  * @see      resolveCommandId
  * @see      resolveElement
  */
function resolveCommand(commandOrId) {
  if (typeof(commandOrId) == 'string') {
    var id = resolveCommandId(commandOrId);
    if (id) {
      return getHTMLElement(id);
    }
  }
  return commandOrId;
}

/**
  * @function resolveCommandId
  * @desc     Qualifies the the command id supplied as
  *           <tt>commandId</tt> and returns the resolved command id.
  *           <p>
  *           If the <tt>commandId</tt> is not prefixed with
  *           <tt>command_</tt> it will be so before returned.
  *
  * @param    commandId Command id; cannot be null.
  * @return   The resolved command id, i.e., prefixed with
  *           <tt>command_</tt>.
  * @see      resolveCommand
  */
function resolveCommandId(commandId) {
  if (commandId) {
    if (commandId.substr(0, 8) != 'command_') {
      commandId = 'command_' + commandId;
    }
    return commandId;
  }
  return null;
}

/**
  * @function resolveNavigatorId
  * @desc     Qualifies the the navigator id supplied as
  *           <tt>navigatorId</tt> and returns the resolved navigator id.
  *           <p>
  *           If the <tt>navigatorId</tt> is not prefixed with
  *           <tt>nav_</tt> it will be so before returned.
  *
  * @param    navigatorId Navigator id; cannot be null.
  * @return   The resolved navigator id, i.e., prefixed with
  *           <tt>nav_</tt>.
  */
function resolveNavigatorId(navigatorId) {
  if (navigatorId) {
    if (navigatorId.substr(0, 4) != 'nav_') {
      navigatorId = 'nav_' + navigatorId;
    }
    return navigatorId;
  }
  return null;
}

// Updates the internal data structures with all registered
// element initial (on load) values...
function initValues() {
  for (var i in fields) {
    var field = fields[i];
    var e = getHTMLElement(field.id);
    if (e) {
      field.value = field.onloadValue = e.value;
      valueChangedHandler(e); // ok?
    }
  }
  initialised = true;
}

/**
  * @function getValue
  * @desc     Returns the value of the element represented by
  *           <tt>elementOrId</tt>.
  *           <p>
  *           If the value is equal to the default value of the
  *           element the empty string is returned. Otherwise the
  *           current value of the element is trimmed and returned.
  *
  * @param    elementOrId Element or element id; cannot be null.
  * @return   The current value; never null.
  */
function getValue(elementOrId) {
  var e = resolveElement(elementOrId);
  if (!e) {
    return "";
  }
  var field = fields[e.id];
  if (field != null) {
    switch (e.type.toLowerCase()) {
      case 'checkbox': {
        return e.checked ? "on" : "";
      }
      default: {
        return resolveValue(field, e.value);
      }
    }
  }
  switch (e.type.toLowerCase()) {
    case 'checkbox': {
      return e.checked ? "on" : "";
    }
    default: {
      return e.value;
    }
  }
}

// Strips the default value from the current value...
function resolveValue(field, value) {
  if ((value == undefined) || (value == null)) {
    return "";
  }
  // Default value for the field...
  var regEx = new RegExp(field.checkValue, "i");
  // Skip leading or trailing spaces...
  var v = value.replace(/^\s+|\s+$/, "");
  // Reduce two or more spaces to a single one...
  v = v.replace(/\s+/g, " ");
  // Remove original value...
  v = v.replace(regEx, "");
  return v;
}

// Adds and performs check of elements...
function check(commandId, id, checkValue, type, parameter, message, reference, primary) {
  if (hasError) {
    return false;
  }

  // Add check...
  if (arguments.length) {
    var field = fields[id];
    if (field == null) {
      fields[id] = field = {id:           id,
                            value:        null,
                            onloadValue:  null,
                            checkValue:   checkValue,
                            types:        [],
                            parameters:   [],
                            messages:     [],
                            references:   [],
                            primaries:    []};
    }
    field.types.push(type);
    field.parameters.push(parameter);
    field.messages.push(message);
    field.references.push(reference);
    field.primaries.push(primary ? 1 : 0);

    if (commandId) {
      var list = commands[commandId = resolveCommandId(commandId)];
      if (list == null) {
        commands[commandId] = list = [];
      }
      list.push(field);
    }

  // Perform check - on submit...
  } else {
    returnPressed = false;
    if (busy) {
      return false;
    }
    var list = commands[getFormCommand()];
    if (list == null) {
      return true;
    }
    for (var i = 0; i < list.length; i++) {
      var field = list[i];
      var error = validate(field.id, true, 0);
      if (error != -1) {
        return validationError(field.id, field.messages[error]);
      }
    }
    deactivate();
    return true;
  }
}

// Validates the element with id "id"...
function validate(id, dontClear, checkPrimary, initial) {
  var field = fields[id];
  if (field == null) return -1;
  var e = getHTMLElement(field.id);
  if (e == null) return -1;
  
  var error = -1;
  if (!checkValidationHandler(field)) {
    return error;
  }

  var original = e.value;
  var value = resolveValue(field, e.value);
  // Now perform the desired validation...

  // Build arrays...
  for (var i = 0; i < field.types.length; i++) {
    var reference = field.references[i];
    var primary   = field.primaries[i];

    if ((reference) && (!primary)) {
      var match = false;
      var f     = null;
      // Build validation list...
      for (var elementId in fields) {
        if (elementId == reference) {
          f = fields[elementId];
          for (var j = 0; j < f.primaries.length; j++) {
            var r = f.references[j];
            if ((r == field.id) && (f.primaries[j])) {
              match = true;
              break;
            }
          }
        }
      }
      if (!match) {
        throw new Error("No reference '" + reference + "' for '" + id + "'");
      }
      // If no error in referenced field, ok to validate the current
      // validation...
      if ((initial == null) || (initial != f.id)) { // Avoid infinite recursion...
        if (validate(f.id, true, 1, initial == null ? f.id : initial) != -1) {
          continue;
        }
      }
    }

    if (checkPrimary != field.primaries[i]) {
      continue;
    }

    // Current parameter, if any...
    var parameter = null;
    if ((field.parameters[i] != undefined) && (field.parameters[i] != null)) {
      parameter = field.parameters[i];
    }
    switch (field.types[i]) {
      case 'empty': {
        if (value == "") {
          error = i;
        }
        break;
      }
      case 'numeric.not': {
        if (value != "") {
          if (value.search(/\d/) != -1) {
            error = i;
          }
        }
        break;
      }
      case 'numeric': {
        if (value != "") {
          if (parameter == null) {
            parameter = "";
          }
          if (parameter == "negative") {
            if (value.replace(/\-\d+/, "")) {
              error = i;
            }
          } else if (parameter == "postive") {
            if (value.replace(/\d+/g, "")) {
              error = i;
            }
          } else {
            if (value.replace(/\-\d+|\d+/g, "")) {
              error = i;
            }
          }
        }
        break;
      }
      case 'numeric.start': {
        if (value != "") {
          if (parameter == null) {
            parameter = "";
          }
          if (parameter == "negative") {
            if (value.search(/^\-\d+/) == -1) {
              error = i;
            }
          } else if (parameter == "postive") {
            if (value.search(/^\d+/) == -1) {
              error = i;
            }
          } else {
            if (value.search(/^\-\d+|^\d+/) == -1) {
              error = i;
            }
          }
        }
        break;
      }
      case 'numeric.end': {
        if (value != "") {
          if (parameter == null) {
            parameter = "";
          }
          if (parameter == "negative") {
            if (value.search(/\-\d+$/) == -1) {
              error = i;
            }
          } else if (parameter == "postive") {
            if (value.search(/\d+$/) == -1) {
              error = i;
            }
          } else {
            if (value.search(/\-\d+$|\d+$/) == -1) {
              error = i;
            }
          }
        }
        break;
      }
      case 'date': {
        break; // Do nothing client side...

      }
      case 'equal': {
        if ((parameter != null) && (value != field.parameters[i])) {
          error = i;
        }
      }
      default: {
      }
    }
  }
  if ((!dontClear) && (original != value)) {
    // Update value?...
    switch (e.tagName.toLowerCase()) {
      case 'input': {
        e.value = value;
        break;
      }
      default:
    }
  }
  hide(field.id + "_error");
  hide(field.id + "_warning");
  return error;
}

function checkValidationHandler(field) {
  return true;
}

/**
  * @function validationError
  * @desc     Displays the validation error message supplied as
  *           <tt>message</tt>, caused by validating the field
  *           represented by <tt>elementOrId</tt>.
  *           <p>
  *           The busy flag for the page is cleared when this function
  *           is invoked.
  *
  * @param    elementOrId Element or element id; cannot be null.
  * @param    message     The error message.
  * @return   Always false.
  */
function validationError(elementOrId, message) {
  if (browser.ie) {
    // Element...
    var e = resolveElement(elementOrId);
    if (e) {
      var id = e.name + "_error";
      // Table error row...
      if ((e = resolveElement(id)) != null) {
        // Last cell...
        var cell = e.cells[e.cells.length - 1];
        cell.innerHTML = "<div class='errortext'>" + message + "</div>"; // FIXME!
        show(id);
      }
    }
  }
  alert(message);
  clearBusy(); // Did not submit...
  return false;
}

// Fetches command type: "text" or "image"...
function getCommandTypes(commandId) {
  var commands = getCommandElements(commandId);
  var types    = [];
  for (var i = 0; i < commands.length; i++) {
    var command = commands[i];
    // Images (masked by a hidden field)...
    if (command.tagName.toLowerCase() == 'img') {
      types.push("image");
    // Input buttons (submit, reset)...
    } else {
      types.push("text");
    }
  }
  return types;
}

// Fetches all relevent command elements...
function getCommandElements(commandId) {
  var elements = [];
  var inputs = document.getElementsByTagName("input");
  var images = document.getElementsByTagName("img");
  if (inputs != null) {
    for (var i = 0; i < inputs.length; i++) {
      var e = inputs[i];
      if (e.className && e.className.indexOf('button') != -1)  {
        elements.push(e);
      }
    }
  }
  if (images != null) {
    for (var i = 0; i < images.length; i++) {
      var e = images[i];
      if (e.className && e.className.indexOf('button') != -1) {
        elements.push(e);
      }
    }
  }
  if (!elements.length) {
    return elements;
  }
  var ids = [];
  if (commandId != undefined) {
    if (typeof commandId == 'string') {
      ids.push(resolveCommandId(commandId));
    } else {
      for (var i = 0; i < commandId.length; i++) {
        ids.push(resolveCommandId(commandId[i]));
      }
    }
  }
  var commands = [];
  for (var i = 0; i < elements.length; i++) {
    var e    = elements[i];
    var name = e.name.replace(/\_image$/g, "");
    var match = ids.length ? false : true;
    for (var j = 0; j < ids.length; j++) {
      if (name == ids[j]) {
        match = true;
        break;
      }
    }
    if (match) {
      commands.push(e);
    }

    /*
    if ((e.type == 'submit') || (e.type == 'reset'))  {
      var match = ids.length ? false : true;
      for (var j = 0; j < ids.length; j++) {
        if (e.name == ids[j]) {
          match = true;
          break;
        }
      }
      if (match) {
        commands.push(e);
      }
    }
    */
  }
  return commands;
}

// Fetches all relevent navigator elements...
function getNavigatorElements(navigatorId) {
  var inputs   = document.getElementsByTagName("input");
  var elements = [];
  if (inputs != null) {
    for (var i = 0; i < inputs.length; i++) {
      var e = inputs[i];
      if (e.name && e.name.indexOf('nav_') != -1)  {
        elements.push(e);
      }
    }
  }
  if (!elements.length) {
    return elements;
  }
  var ids = [];
  if (navigatorId != undefined) {
    if (typeof navigatorId == 'string') {
      ids.push(resolveNavigatorId(navigatorId));
    } else {
      for (var i = 0; i < navigatorId.length; i++) {
        ids.push(resolveNavigatorId(navigatorId[i]));
      }
    }
  }
  var navigators = [];
  for (var i = 0; i < elements.length; i++) {
    var e     = elements[i];
    var match = ids.length ? false : true;
    for (var j = 0; j < ids.length; j++) {
      if (e.name == ids[j]) {
        match = true;
        break;
      }
    }
    if (match) {
      navigators.push(e);
    }
  }
  return navigators;
}

/**
  * @function deactivate
  * @desc     Deactivates the command with id <tt>commandId</tt>. However,
  *           if <tt>commandId</tt> is null, all commands on the page
  *           will be deactivated.
  *           <p>
  *           A deactivated command cannot be invoked and must be activated
  *           before it can submit the form again.
  *
  * @param    commandId Command id or array of command ids; can be null
  *                     and is resolved. If null all commands on the
  *                     page will be deactivated.
  * @see      activate
  * @see      isActive
  */
function deactivate(commandId) {
  var commands = getCommandElements(commandId);
  function handler(id) {
    return function() {return commandInactive(id)};
  }
  for (var i = 0; i < commands.length; i++) {
    var c = commands[i];
    if ((c.className.indexOf('button') != -1) && (c.className.indexOf('inactive') == -1)) {
      // buttoninactive or imagebuttoninactive...
      c.className += 'inactive';
      if (c._onclick == null) {
        c._onclick = c.onclick;
      }
      c.onclick = handler(c.name);
    }
  }
}


/**
  * @function activate
  * @desc     Activates the command with id <tt>commandId</tt>. However,
  *           if <tt>commandId</tt> is null, all commands on the page
  *           will be activated.
  *           <p>
  *           Only an activated command can be invoked to submit a
  *           form.
  *
  * @param    commandId Command id or array of command ids; can be null
  *                     and is resolved. If null all commands on the
  *                     page will be activated.
  * @see      deactivate
  * @see      isActive
  */
function activate(commandId) {
  var commands = getCommandElements(commandId);
  for (var i = 0; i < commands.length; i++) {
    var c = commands[i];
    if ((c.className.indexOf('button') != -1) && (c.className.indexOf('inactive') != -1)) {
      c.className = c.className.replace(/inactive/i, "");
      c.onclick = c._onclick;
    }
  }
}

/**
  * @function isActive
  * @desc     Returns true if the command or commands represented by
  *           <tt>commandId</tt> is active, false if not. If <tt>commandId</tt>
  *           is null, all commands on the page are tested.
  *           <p>
  *           For this function to return true, all commands in question
  *           must be active.
  *
  * @param    commandId Command id or array of command ids; can be null
  *                     and is resolved. If null all commands on the
  *                     page will be tested.
  * @see      activate
  * @see      deactivate
  */
function isActive(commandId) {
  var commands = getCommandElements(commandId);
  for (var i = 0; i < commands.length; i++) {
    if (commands[i].className.indexOf('inactive') != -1) {
      return false;
    }
  }
  return true;
}

// Returns a single HTML element regardless of browser...
function getHTMLElement(id) {
  var e = document.getElementById(id);
  if (e != null) {
    return e;
  }
  if ((e = document.getElementsByName(id)) != null) {
    if (e.length == 1) {
      return e[0];
    }
  }
  return null;
}

// Returns the default command id of the form...
function getDefaultCommand() {
  var e = getHTMLElement(getForm().id + "_default");
  if (e) {
    return resolveCommandId(e.value);
  }
  return null;
}

// Updates the form with the command supplied as commandId...
function setCommand(commandId, commandValue) {
  // Assume a single form...
  var form = getForm();
  var id   = resolveCommandId(commandId);
  // Add form "value", i.e., the hidden field representing the
  // form value when submitted; the value will contain the
  // command id of the command activated...

  // NOTE: cannot use form name, since form elements may have
  //       an id of 'name'! Assumes no form elements have the
  //       name 'id'...
  var elements = document.getElementsByName(form.id); // form and hidden element, if any...
  for (var i = 0; i < elements.length; i++) {
    var e = elements[i];
    if ((e.tagName.toLowerCase() == 'input') &&
        (e.type.toLowerCase()    == 'hidden')) {
      e.value = id;
      break;
    }
  }
  // Set value, if any...
  setCommandValue(id, commandValue);
  // Update action (if need be)...
  form.action = getCommandAction(id);
}

// Updates the form with the command used by the navigator and then
// submits the form...
function setNavigator(navigatorId, index) {
  var id = resolveNavigatorId(navigatorId);
  // Set navigator index (only one navigator with this id)...
  var element = getNavigatorElements(id)[0];
      element.value = index;
  // Assume a single form...
  var form = getForm();
  // Add form "value", i.e., the hidden field representing the
  // form value when submitted; the value will contain the
  // command id of the command activated...

  // NOTE: cannot use form name, since form elements may have
  //       an id of 'name'! Assumes no form elements have the
  //       name 'id'...
  var elements = document.getElementsByName(form.id); // form and hidden element, if any...
  for (var i = 0; i < elements.length; i++) {
    var e = elements[i];
    if ((e.tagName.toLowerCase() == 'input') &&
        (e.type.toLowerCase()    == 'hidden')) {
      e.value = id;
      break;
    }
  }

  // Update action from associated command (if need be)...
  if ((element = getHTMLElement(id + "_command")) != null) {
    form.action = getCommandAction(element.id);
  }

  formSubmit(false);

}

// Return the current activate command for the form, i.e., the
// command that will be activated when the form is submitted...
function getFormCommand() {
  // Assume a single form...
  var form = getForm();
  // NOTE: cannot use form name, since form elements may have
  //       an id of 'name'! Assumes no form elements have the
  //       name 'id'...
  var elements = document.getElementsByName(form.id); // form and hidden element, if any...
  for (var i = 0; i < elements.length; i++) {
    var e = elements[i];
    if ((e.tagName.toLowerCase() == 'input') &&
        (e.type.toLowerCase()    == 'hidden')) {
      return e.value;
    }
  }
}

// Return the command action of the command with commandId...
function getCommandAction(commandId) {
  var e = getHTMLElement(commandId = resolveCommandId(commandId));
  if (e) {
    var a = getHTMLElement(commandId + "_action");
    if (a) {
      return a.value;
    }
  }
  return getForm().action; // default...
}

// Return the command value of the command with commandId...
function getCommandValue(commandId) {
  var element = getHTMLElement(resolveCommandId(commandId));
  if (element) {
    return element.value;
  }
  return null;
}

// Sets the command value of a command...
function setCommandValue(commandId, commandValue) {
  if ((commandValue != undefined) && (commandValue != null)) {
    var element = getHTMLElement(resolveCommandId(commandId));
    if (element) {
      element.value = commandValue;
    } else {
      throw new Error("Missing command element: " + commandId);
    }
  }
}

// Notifies the first validation error...
function notifyError(id, message, commandId) {
  if (notified) {
    return;
  }
  notified = true;
  addHandler("onload", function () {
                         if (commandId) {
                           commandId = resolveCommandId(commandId);
                           if (confirm(message)) {
                             setCommand(commandId);
                             formSubmit(false);
                           } else {
                             focus(id);
                           }
                         } else {
                           alert(message);
                           focus(id);
                         }
                       });
}

// Internal focus function for elements...
function focus(elementOrId) {
  if (elementOrId) {
    var e = resolveElement(elementOrId);
    try {
      e.focus();
      e.select();
    } catch (exception) {
    }
  }
}

/**
  * @function show
  * @desc     Displays the element represented by <tt>elementOrId</tt>.
  *           <p>
  *           If <tt>elementOrId</tt> is an id and it cannot be found,
  *           this function does nothing.
  *
  * @param    elementOrId Element or element id; cannot be null.
  * @see      hide
  * @see      toggle
  */
function show(elementOrId) {
  var e = resolveElement(elementOrId);
  if (e != null) {
    e.style.display = '';
  }
}

/**
  * @function isShown
  * @desc     Returns true if the element represented by <tt>elementOrId</tt>
  *           is shown, false if not.
  *           <p>
  *           If <tt>elementOrId</tt> is an id and it cannot be found,
  *           this function returns false.
  *
  * @param    elementOrId Element or element id; cannot be null.
  * @return   True if shown, false if not.
  * @see      show
  * @see      hide
  */
function isShown(elementOrId) {
  var e = resolveElement(elementOrId);
  if (e != null) {
    return (e.style.display != 'none');
  }
  return false;
}

/**
  * @function hide
  * @desc     Hides the element represented by <tt>elementOrId</tt>.
  *           <p>
  *           If <tt>elementOrId</tt> is an id and it cannot be found,
  *           this function does nothing.
  *
  * @param    elementOrId Element or element id; cannot be null.
  * @see      show
  * @see      toggle
  */
function hide(elementOrId) {
  var e = resolveElement(elementOrId);
  if (e != null) {
    e.style.display = 'none';
  }
}

/**
  * @function toggle
  * @desc     Display the element represented by <tt>elementOrId</tt>
  *           if it is currently hidden, or vice versa.
  *           <p>
  *           If <tt>elementOrId</tt> is an id and it cannot be found,
  *           this functions does nothing.
  *           <p>
  *           The <tt>src</tt> attribute of the image represented by
  *           <tt>imageOrId</tt> can changed as well, if not null. If
  *           the element represented by <tt>elementOrId</tt> is shown,
  *           the <tt>src</tt> attribute is set to <tt>showSrc</tt>; if
  *           hidden, the <tt>src</tt> attribute is set to <tt>hideSrc</tt>.
  *
  * @param    elementOrId Element or element id; cannot be null.
  * @param    imageOrId   Image to be changed when the element is shown
  *                       or hidden; can be null.
  * @param    showSrc     Image path when the element is shown; cannot
  *                       be null if <tt>imageOrId</tt> is not null.
  * @param    hideSrc     Image path when the element is not shown; cannot
  *                       be null if <tt>imageOrId</tt> is not null.
  * @see      show
  * @see      hide
  * @return   True if the element is now shown, false if hidden.
  */
function toggle(elementOrId, imageOrId, showSrc, hideSrc) {
  var e = resolveElement(elementOrId);
  if (e != null) {
    var img = imageOrId;
    if (typeof img == 'string') {
      img = getHTMLElement(img);
    }
    if (e.style.display == 'none') {
      e.style.display = '';
      if (img) {
        img.src = showSrc;
      }
      return true;
    } else {
      e.style.display = 'none';
      if (img) {
        img.src = hideSrc;
      }
      return false;
    }
  }
}

/**
  * @function showPage
  * @desc     Displays the page with <tt>pageId</tt> in the browser
  *           window with id <tt>windowId</tt>. If such a window is
  *           already open the contents will be replaced with that
  *           of the new page.
  *
  * @param    pageId   The page id of the page to display; cannot be null.
  * @param    windowId The browser window name; can be null in which
  *                    case <tt>window</tt> is used.
  * @param    w        The width of the window; default 800 pixels.
  * @param    h        The height of the window; default 400 pixels.
  * @see      showCommand
  */
function showPage(pageId, windowId, w, h) {
  return showWindow(pageId, windowId, w, h);
}

/**
  * @function showCommand
  * @desc     Executes the command with id <tt>commandId</tt> and displays
  *           the result in the browser window with id <tt>windowId</tt>.
  * @comment  No validation is performed!
  *
  * @param    commandId The command id of the command to execute; cannot be null.
  * @param    windowId  The browser window name; can be null in which
  *                     case <tt>window</tt> is used.
  * @param    w         The width of the window; default 800 pixels.
  * @param    h         The height of the window; default 400 pixels.
  * @see      showPage
  */
function showCommand(commandId, windowId, w, h) {
  var id = resolveCommandId(commandId);
  return showWindow(getCommandAction(id) + "?" + id + "=" + getCommandValue(id), windowId, w, h);
}

// Open a new browser window...
function showWindow(action, windowId, w, h) {
  var id     = windowId ? windowId : 'window';
  var width  = w        ? w        : 800;
  var height = h        ? h        : 400;
  if (id.substr(0, 8) != 'revireg_') {
    id = 'revireg_' + id;
  }
  try {
    var win = windows[id];
    if ((win != null) && (!win.closed)) {
      if ((win.location.href.search(action) != -1) || (confirm("Vinduet er ved at vise en anden side!\n\nVil du gå væk fra den?"))) {

        win.location.href = action;
        win.resizeTo(width, height);
      }
    } else {
      windows[id] = win = window.open(action, id, 'width=' + width + ',height=' + height + ',scrollbars,toolbar=no,status=no,menubar=no,resizable=no');

    }
    win.focus();

  } catch (e) { // Since window may be closed due to pop-up stoppers...
  }
  return false;
}

/**
  * @function addHandler
  * @desc     Adds an event handler to a given element. This function
  *           allows several handlers to be added to the same element.
  *           <p>
  *           The are two ways of invoking this function: <ol>
  *      <li> <tt>var handler = addHandler(eventname, handler);</tt> or
  *      <li> <tt>var handler = addHandler(object, eventname, handler);</tt>
  *           </ol>
  *           The first one attaches <tt>handler</tt> to the event
  *           identified by <tt>eventname</tt> on the global window object.
  *           The second attaches <tt>handler</tt> to the event identified
  *           by <tt>eventname</tt> on <tt>object</tt>.
  *           <p>
  *           The event name must be fully qualified, e.g., "onload",
  *           "onclick", etc.
  *
  * @param    arguments[0] Object to add handler <b>or</b> fully qualified
  *                        event name, e.g., "onload". If an event name
  *                        the eventhandler is attached to the window
  *                        object.
  * @param    arguments[1] Fully qualified event name, e.g., "onload", <b>or</b>
  *                        the handler to be invoked when the event is
  *                        fired.
  * @param    arguments[2] Handler to be invoked when the event is fired.
  * @example  Add an <tt>onload</tt> handler to the <tt>window</tt> object:
  *           <pre>
  *             addHandler("onload", function() {alert("onload!")});
  *           </pre>
  * @example  Add an <tt>onclick</tt> handler to the INPUT element with id
  *           <tt>foo</tt>:
  *           <pre>
  *             addHandler(document.getElementById("foo"), "onclick", function() {alert("onclick!")});
  *           </pre>
  */
function addHandler() {
  var i = (typeof(arguments[0]) == 'object');
  var e = (i) ? arguments[0] : window;
  var p = (i) ? arguments[1] : arguments[0];
  var h = (i) ? arguments[2] : arguments[1];
  if ((p) && (h)) {
    p = p.toLowerCase();
    var old = e[p];
    if (old) {
      e[p] = function () {
               var res = old();
               res = h(res);
               if (res != undefined) {
                 return res;
               }
             };
    } else {
      e[p] = h;
    }
  }
}

// Redirect to url...
function redirect(url) {
  location.href = url;
}

// Alers an (internal) error message and redirects...
function showError(message) {
  alert('Der er desværre opstået en JavaScript fejl:\n\n' + message + '\nUndskyld ulejligheden!');
  try {
    showWindow('view-source:' + this.location, 'source');
    alert('Ved henvendelse angående fejl, gem venligst HTML kildekoden for at lette fejlfindingen.');
  } catch (e) {
  }
  redirect('../browsers.htm');
}

// Handlers...
document.onkeydown = handleReturn;
window.onload = initValues;
window.onbeforeunload = function() {
                          if (deac) {
                            deactivate();
                          }
                        };
window.onerror = function alertError(name, desc, number) {
                   hasError = true;
                   if (browser.ie) {
                     event.cancleBubble = true;
                   }
                   var stackTrace = null;
                   try {
                     stackTrace = arguments.callee.getTrace();
                     stackTrace.shift(); // remove this function from stack
                   } catch (e) {
                   }
                   var id = desc.split(/\//).pop();
                   showError('  -- Beskrivelse: ' + name + '\n  -- Side: ' + id + '\n  -- Linie: ' + (number - 1) + '\n  -- Trace:\n' + ((stackTrace == null) ? '      --> Unavailable' : stackTrace.join('\n')) + '\n');
                   return true;
                 };

// Add name() method to Function object...
Function.prototype.getName =
  function() {
    var name = this.toString().replace(/^\s*function\s*/, '');
    if (name.charAt(0) == '(') {
      return 'anonymous';
    }
    return name.split(/\s*\(/)[0];
  }

// Add trace method to Function object...
Function.prototype.getTrace =
  function() {
    // Caller is unfortunately deprecated as of 1.3, but still works
    // in Explorer...
    var caller = arguments.callee.caller;
    var parts    = [];
    var previous = "";
    var j        = 0; // precaution...
    while (caller != null && j++ < 25) {
      var current = caller.getName() + "(";

      for (var i = 0; i < caller.arguments.length; i++) {
        if (i > 0) {
          current += ", ";
        }
        var a = caller.arguments[i];
        if (a == undefined) {
          current += "undefined";
        } else if (a == null) {
          current += "null";
        } else {
          var t = (typeof a);
          current += t + ": ";
          if (t == 'function') {
            current += a.getName();
          } else {
            current += a.toString();
            if (a.id) {
              current += "{id: " + a.id + "}";
            } else if (a.name) {
              current += "{name: " + a.name + "}";
            }
          }
        }
      }
      current += ")";
      if (current == previous) {
        parts.push("      --> **infinite loop!**");
        break;
      }
      parts.push("      --> " + current);
      caller = caller.arguments.callee.caller;
      previous = current;
    }
    return parts;
  }

