// =======================================================================
//  Generic form defang utilities
// =======================================================================
defangForm = {
  defaultLang : "en",
  scopeElm    : null,

  // Utility to make reset-buttons more harmless by adding a confirmation step.
  // Markup for buttons: <input type="button" class="reset" value="Clear">
  resetButtons : {
    is : { "alertTxt" : "Ath: Þú ert í þann mund að afturkalla öll innslegin gildi..." },
    en : { "alertTxt" : "Note: You are about to reset all values in the form..." },
    buttonClass : "reset",  // className which indicates which `<input type="button" />` elements get turned into a "safe" reset-button.

    init : function()
    {
      var _theFields = DOM.get('input.'+this.buttonClass +', input[type=reset], .'+this.buttonClass+' input');
      for (var i = 0, _theField; _theField = _theFields[i]; i++)
      {
        if (!_theField.onclick)
        {
          EEvent.add(_theField, "click", this._handleReset);
        }
      }
      return true;
    },

    _handleReset : function(e)
    {
      var _propLang = DOM.getLang(this);  // get the lang value for the button.
      if (!_propLang || !defangResets[_propLang] ) { _propLang = defangForm.defaultLang; }
      if (confirm(defangResets[_propLang].alertTxt))  // Ask the user to confirm the reset action
      {
        if (this.type == "button") { this.form.reset(); }  // reset the form if the <input> is type="button"
        return true;  // accept the click (and pass it through to other "onReset" functions).
      }
      return false;  // Cancel the click by default.
    }

  },


  // Utility to disable "Enter Key submits" from within <input type="(text|file|password|checkbox|radio)" ... /> fields
  enterKey : {
    disableAll : true,  // indicates whether all `<form>`s should have Enter disabled by default.
    exceptList : [],  // array of DOM-IDs of <form>s that should be handled differently from the rest
                      // (ie, if disableAll==true, then don't disable these forms. And vice-versa.)
    emulateTab : false, // should Enter emulate the behaviour of the Tab-key (ie. move the focus to the next field)

    init : function()
    {
      var _makeException = "add";
      if (this.disableAll)
      {
        var _theForms = DOM.get("form", defangForm.scopeElm);
        for (var i = 0, _theForm; _theForm = _theForms[i]; i++)
        {
          EEvent.add(_theForm, "keypress", this._handleEnter);
        }
        _makeException = "remove";
      }
      // find the forms to make exceptions for
      for (var i = 0, _id; _id = this.exceptList[i]; i++)
      {
        var _theForm = DOM.$(_id);
        if (_theForm) { Event[_makeException](_theForm, "keydown", this._handleEnter); }
      }
      return true;
    },


    _handleEnter : function(e)
    {
      var _target = e.target,
          _tagName = _target.tagName.lc(),
          _aboutToSubmit = !(
            (e.keyCode != 13)  ||
            (_tagName != "input") ||
            (_target.type && ("button|reset|submit".indexOf(_target.type) > -1))
          );
      if (_aboutToSubmit || (e.keyCode == 13 && defangForm.enterKey.emulateTab && _tagName == "select"))
      {
        if (!defangForm.enterKey.emulateTab) { return false; }

        var found = (_target == this.elements[0]);
        for (var i = 1, _theField; _theField = this.elements[i]; i++)
        {
          if (!autoValidate._validFieldTagNames.test(_theField.tagName)) { continue; }
          if (found)
          {
            setTimeout(function(){ _theField.focus(); }, 10);
            break;
          }
          found = (_target == _theField);
        }
        return (_tagName == "select");  // don't kill the Enter key-stroke if the target is a <select>
      }
      return true;
    }

  }

};


defangResets = defangForm.resetButtons;  // left in for backwardscompatibility












// =======================================================================
// Automatic client-side forms validation
// =======================================================================

autoValidate = {

  _alreadyRun : false,

  lang           : "en",  // Two digit language-code used for displaying error messgages, etc.  (This is default language used if the current document/element has a `lang=""` attribute that isn't supported by the script.)
  defaultCssFile : null,  // URL to a custom CSS file  null == auto-find default CSS file -- "" == use no CSS file.
  initAllForms   : true,                         // if false then search up the DOM tree for "inclPattern" to trigger AV
  inclPattern    : /\b(do)?validate\b/i,        // className pattern that activates the validator when !initAllForms
  skipPattern    : /\b(no|dont)validate\b/i,    // className pattern that *always* deactivates the validator
  bullet         : " * ",                       // the prefix (bullet) added to each error listed in the error alert() window.
  maxLabelLength : 35,                          // number of characters. Cutoff-limit for <label> values that get listed in the error alert() window.
  errorAction    : "focus",                     // available values: (anchor|focus)  (Note: anchor is bound to be more irritating.)
  focusElmClass  : "stream",                    // className for the placeholder <a href=""> element used to move the keyboard/screen-reader focus between inline error messages.
  submittedClass : "issubmitted",               // className that gets added to the <form> element when it has been submitted and is waiting for the server to respond

  validateEachField   : "",  // possible values:  "change"==onChange;  "blur"==onBlur;  ""==onSubmit (normal)
                             // NOTE: "blur" is mostly evil.   IE seems to focus() the next field before resolving the onblur event on the current field.
  errorMsgType        : "alertonly",  // How should error texts be displayed?
                                      // Possible values: "alertonly", "inlineonly", "both". Defaults to alertonly.
  labelBeforeErrorMsg : 0,  // designates the position of the inline error message relative to the field's `<label>`
                            // possible values: 1==<label><error>;  0==<error><label>
  inlineErrorClass    : "errmsg",     // className for the inline "Error" message element
  nextErrorLinkClass  : "nexterror",  // className for the inline "Next Error" link element

  en : {  // English translation
    errorReqMsg    : "Please fill out these fields:\n\n",       // Header for the list of empty "required" fields in the error alert() window.
    errorTypeMsg   : "These fields contain invalid input:\n\n", // Header for the list of "invalid value" fields in the error alert() window.

    inlineMsgPrefix : "Error:",                                 // Prefix/header for *all* inline errors.
    inlineReqMsg    : "This field is required ",                // Default "value required" inline error message.
    inlineTypeMsg   : "This field contains an invalid value ",  // Default "invalid value" inline error message.
    inlineNextError : "Next error",                             // Text for the "Next Error" link element.

    resubmitMsg    : ""  // Optional alert message that pops up when the user tries to submit valid form-values many times. If this property is left empty (""), the validator won't try to stop the user from submitting multiple times.
  },

  is : {  // Icelandic translation
    errorReqMsg    : "Það þarf að fylla út þessa liði:\n\n",
    errorTypeMsg   : "Þessir liðir eru rangt útfylltir:\n\n",

    inlineMsgPrefix : "Villa:",
    inlineReqMsg    : "Það þarf að fylla út þennan lið ",
    inlineTypeMsg   : "Þessi liður er rangt út fylltur ",
    inlineNextError : "Næsta villa",

    resubmitMsg    : ""
  },

  types       : {},  // Container for custom validation checks based on autoValidate.fiClassPattern prefixed classNames
  customReqCheck : {},  // Container for dynamic "required" checks for based on field @name attribute values.
  customTypeCheck : {},  // Container for dynamic "valid-type" checks for based on field @name attribute values.

  fiClassPattern  : /\bfi_\w\w/,   // The `className` prefix used to designate of which `type` the field is for custom validation checking. (used by the .findParent() method)
                                    // (The element with that sort of `className` considered to be the field's "container" element.)
  reqClassPattern : /\breq\b/,     //  The `className` that designates a field to be "required".

  reqErrorClass  : "reqerror",      // The `className` put on field "container" elements when they trigger a "required" error.
  typeErrorClass : "typeerror",     // The className put on field "container" elements that have a "invalid input" error.

  _reqPassed : {}, // runtime storage for information about which fields passed a req check and which didn't
  _validFieldTagNames : /input|select|textarea/i,  // what sort of form.elements are considered to be actual "input fields".




  init : function(_domScope)
  {
    if (!document.getElementsByTagName) { return true; }

    if (!this._alreadyRun  &&  this.defaultCssFile !== "")  // if this.defaultCssFile === "" then assume the user doesn't want any remote CSS file
    {
      if (!this.defaultCssFile)  // if this.defaultCssFile is undefined or null we then auto-find the correct URL.
      {
        var _scripts = DOM.get("script");
        var i = _scripts.length;
        while (i--)
        {
          var _item = _scripts[i];
          if (_item.src && _item.src.indexOf("autovalidate/1.2/av") > -1)
          {
            this.defaultCssFile = _item.src.replace(/[^\/]+$/, "av.css?v=0");
            break;
          }
        };
      }
      // Insert the CSS file
      DOM.insertLink(this.defaultCssFile);
    }
    // prevent redundant repeat runs
    this._alreadyRun = true;

    // Find and loop through all the forms on the page.
    _domScope = _domScope || document.body;
    var _theForms = _domScope.tagName.lc()=="form" ? [_domScope] : DOM.get("form", _domScope);

    for (var g = 0, _theForm; _theForm = _theForms[g]; g++)
    {
      // compile a superstring of classNames of the form and all its ancestor elements.
      var _classNames = '',
          _container = _theForm;
      while (_container = _container.parentNode)
      {
        _classNames += " " + _container.className;
      }

      // determine if we should init this particular form.
      var _doInitThisForm = this.initAllForms;
      if (_doInitThisForm)  // we should initAllForms by default (including this one)
      {
        _doInitThisForm = !this.skipPattern.test(_classNames);  // check if we should skip this one
      }
      else  // exclude all forms by default
      {
        _doInitThisForm = this.inclPattern.test(_classNames);  // check if this form should be included anyway
      }

      // perform the initialization
      if (_doInitThisForm)
      {
        if (this.validateEachField) { autoValidate.prepareTheForm(_theForm); }  // otherwise the onchange/onblur events don't fire...
        EEvent.add(_theForm, "submit", this.formSubmitHandler);
      }
    }
  },


  _fieldChangeHandler : function(e)
  {
    var _isValid = autoValidate.isValid(this.parentNode);  // TODO: see if we can change isValid() to accept single fields.
    // return _isValid || msie.version<8;
    return true;  // must not return false here.. .because of stupid IE bug
  },


  formSubmitHandler : function(e)
  {
    // if (!this.onsubmitAVBackup() || !autoValidate.isValid(this))
    if (!autoValidate.isValid(this))
    {
      return false;
    }
    else
    {
      // prevent multiple submits
      if (this.isSubmitted && autoValidate[this.avLang].resubmitMsg)
      {
        alert(autoValidate[this.avLang].resubmitMsg);
        return false;
      }
      this.isSubmitted = true;
      DOM.addClass(this, autoValidate.submittedClass);
    }
    return true;
  },





  prepareTheForm : function(_theContainerElm)
  {
    var _theForm = _theContainerElm;
    while(_theForm.tagName.lc() != "form" && _theForm.parentNode != document)
    {
      _theForm = _theForm.parentNode;
    }

    // figure out the appropriate autoValidation language for _theForm.
    // side-benefit: running .getLang() on _theForm sets its html@lang attribute to speed up later .getLang() requests on its descendant nodes
    var _propLang = DOM.getLang(_theForm);
    _theForm.avLang = (this[_propLang]) ? _propLang : this.lang;

    // First make all form-fields reference their <label>
    this._prepareLabels(_theForm);

    // Determine if each field should be validated onBlur/onChange.
    var _fieldEvent = this.validateEachField;
    if (_fieldEvent != "blur" && _fieldEvent != "change") { _fieldEvent = ""; }

     // Loop through all the form-fields
    _theForm.avFields = [];
    for (var f = 0, _theField; _theField = _theForm.elements[f]; f++)
    {
      // skip <fieldset>s and <optgroups> and such...
      if (this._validFieldTagNames.test(_theField.tagName))
      {
          // find the 'active' container/"parent" element of _theField.
          this.findParent(_theField);
          DOM.aquireId(_theField.avParent); // make sure the avParent (created by findParent) has an @id

          // Figure out _theField's language value
          if (!_theField.avLang)
          {
            _propLang = DOM.getLang(_theField);
            _theField.avLang = (this[_propLang]) ? _propLang : _theForm.avLang;
          }

          this._determineValidationChecks(_theField);  // Check to see if the field's dataType has a custom requirement check assigned to it...

          // if needed, assign an onBlur/onChange validation...
          if (_fieldEvent)
          {
            EEvent.add(_theField, _fieldEvent, this._fieldChangeHandler);
          }
          // ----------------------------------------------------------------
          // TODO: Here would be a good spot to add custom field onblur/onchange handling... (from a config array with ID keys)
          // ----------------------------------------------------------------

        // push _theField to the forms avFields array, and increase the Dirty Counter
        _theForm.avFields.push(_theField);
      }
    }
  },




  _prepareLabels : function(_theForm)  // Make all form-fields reference their <label> element.
  {
    var _labels = DOM.get("label", _theForm);
    for (var i = 0, labelElm; labelElm = _labels[i]; i++)
    {
      if (labelElm.avDone) { continue; }  // only do this once
      labelElm.avDone = true;
      var _theField = DOM.$( labelElm.getAttribute("for") || labelElm.htmlFor );
      if (_theField) { _theField.labelElm = labelElm; }
    }
  },



  getFormElements : function(_containerElm)
  {
    var _theForm = _containerElm;
    while(_theForm.tagName.lc() != "form" && _theForm.parentNode != document)
    {
      _theForm = _theForm.parentNode;
    }
    _containerElm.avLang = _containerElm.avLang || _theForm.avLang;

    var _enteredScope = false;
    var _theFormElements = [];
    for (var i = 0, item; item = _theForm.avFields[i]; i++)
    {
      if ( DOM.isAncestor(_containerElm, item) )
      {
        _theFormElements.push(item);
        _enteredScope = true;
      }
      else if (_enteredScope)
      {
        break; // because we want to exit asap.
      }
    }
    return _theFormElements;
  },



  findParent : function(_theField) // find the 'active' container/"parent" element of _theField.
  {
      var avParent = _theField; // Allow the "req" and "fi_xxx" classNames to reside on the field-element itself (a field to be it's own "parent").
      _theField.isRequired = false;
      while ( avParent.tagName.lc() != "form"
              && !(_theField.isRequired = this.reqClassPattern.test(avParent.className))  // while we're at it assign the _theField.isRequired value
              && !this.fiClassPattern.test(avParent.className)
            )
      {
        avParent = avParent.parentNode;  // or on one of it's ancestor elements - upto, but not including the <form>.
      }
      _theField.avParent = avParent;
    return true;
  },




  _determineValidationChecks : function(_theField)
  {
    _theField.avCheck = {};

    // Check to see if the field's of a dataType has a default validation assigned to it...
    if ( _theField.tagName.lc()=="input" && (!_theField.type || "text|file|password".indexOf(_theField.type) > -1) )
    {
      var _classNames = _theField.avParent.className.split(" ");
      for (var i = 0, _className; _className = _classNames[i]; i++)
      {
        if (this.types[_className])
        {
          _theField.avCheck = this.types[_className];
          break;
        }
      }
    }

    // Check to see if the field(.name) has a custom "valid type" check assigned to it...
    if (this.customTypeCheck[_theField.name])
    {
      _theField.avCustomTypeCheck = this.customTypeCheck[_theField.name];
    }

    // Check to see if the field(.name) has a custom "required" check assigned to it...
    var customReqCheck;
    if (customReqCheck = this.customReqCheck[_theField.name])
    {
      _theField.avCustomReqCheck = (typeof(customReqCheck) == "string") ? function() {
        // Perform a simple (backwards-lookup) requirement dependency check
        return ( !autoValidate._reqPassed[ customReqCheck.replace(/^!/,"") ]  ==  (customReqCheck.substr(0,1)=="!") );
      } : customReqCheck;
    }


    return true;
  },


  _purgeErrorMessages : function(_theField)
  {
    _theField.errorMsgInline = "";
    _theField.errorMsg = "";  // custom property that holds any error Messages set by the custom type/req validation functions.
                              // Its value is read/alerted to the user when the validation script has run.
    if (_theField._errMsgElm)
    {
      DOM.removeNode(_theField._errMsgElm);
      DOM.removeNode(_theField._nextErrLinkElm);
      _theField._errMsgElm = null;
      _theField._nextErrLinkElm = null;
    }
    DOM.removeClass(_theField.avParent, this.reqErrorClass);
    DOM.removeClass(_theField.avParent, this.typeErrorClass);

    return true;
  },



  _injectInlineErrors : function(_theFields)
  {
    var _texts = this[_theFields[0].form.avLang];

    // prepare a prototype next link.
    var _nextErrLink0 = document.createElement("a");
    _nextErrLink0.className = this.nextErrorLinkClass;
    _nextErrLink0.innerHTML = _texts.inlineNextError;

    // loop through the fields
    for (var i = 0, _theField; _theField = _theFields[i]; i++)
    {
      // Build the _errMsgElm
      var _errMsgElm = document.createElement("strong");
      _errMsgElm.className = this.inlineErrorClass;
      if (_theField.errorMsgInline)
      {
        _errMsgElm.innerHTML = _theField.errorMsgInline;
      }
      else
      {
        _errMsgElm.innerHTML = (this._reqPassed[_theField.name]) ? _texts.inlineTypeMsg : _texts.inlineReqMsg;
        if (_theField.errorMsg) { _errMsgElm.innerHTML += " ("+_theField.errorMsg+")"; }
      }

      // Build link to the next error
      var _nextErrLinkElm = document.createTextNode("");
      if (_theFields[i+1])    // Only if we're NOT on the final error
      {
        _nextErrLinkElm = _nextErrLink0.cloneNode(-1);
        _nextErrLinkElm.href = "#"+_theFields[i+1].avParent.id;
      }


      // make sure the field has a "real" and usable avParent
      if (_theField.avParent.id != _theField.id  &&  _theField.avParent.tagName.lc() != "form")
      {
        // place the error Message
        if (this.labelBeforeErrorMsg && _theField.labelElm)
        {
          DOM.insertAfter(_errMsgElm, DOM.firstChildTag(_theField.avParent));
        }
        else
        {
          DOM.prependChild(_errMsgElm, _theField.avParent);
        }
        DOM.appendChild(_nextErrLinkElm, _theField.avParent)  // place the nextErrorLink at the end of the avParent container
      }
      else // There's no real field-container (avParent is either the form or the field itself).
      {
        DOM.insertBefore(_errMsgElm, _theField);   // Place the errorMsg before the field
        DOM.insertAfter(_nextErrLinkElm, _theField)  // place the nextErrorLink directly after the field
      }

      _theField._errMsgElm = _errMsgElm;
      _theField._nextErrLinkElm = _nextErrLinkElm;

    }
    return;
  },






  isValid : function(_containerElm, _returnSilently)
  {
    this.prepareTheForm(_containerElm);  // we're forced to do this every time because form fields might have been added/removed since last time.

    var _theFormElements = 
      (_containerElm.tagName.lc() == "form") ?
          _containerElm.avFields:
      (_containerElm.avParent) ?
          [_containerElm]:
          this.getFormElements(_containerElm);

    var _errorReqAlert = "";      // Message strings for missing required fields
    var _errorTypeAlert = "";     // Message strings for wrongly typed text input and checkboxes.

    var _errorFields = [];  // array with references to all fields with errors.
    this._reqPassed  = {};  // list of all fields that have have been found to have a non-empty value (crude hack used by the dynamic autoValidate.customReqCheck mechanism).

    var _firstErrorField = null;  // TODO: see if we can depricate this "hack" and use _errorFields[0] instead.
    var _beenHere = {};  // "hack" to validate checkbox and radio-button groups.

    for (var f = 0, _theField; _theField = _theFormElements[f]; f++)  // Loop through all fields in this form
    {
      if (_theField.disabled) { continue; }

      // Preform/run custom the requirement check
      if (_theField.avCustomReqCheck) { _theField.isRequired = _theField.avCustomReqCheck(_theField); }
      else if (_theField.avCheck.isRequired) { _theField.isRequired = _theField.avCheck.isRequired(_theField); }



      // perform normal requirement check
      var _tagName = _theField.tagName.lc(),
          _somethingIsSelected = false;
      // First Validate textarea, text input fields, and select menus
      if ( ( (_tagName == "input") && ( !_theField.type || "text|file|password".indexOf(_theField.type) > -1) ) ||
           (_tagName == "select") || (_tagName == "textarea")
         )
      {
        this._purgeErrorMessages(_theField);

        if  (_tagName == "select")
        {
          for (var k = 0, _theOption; _theOption = _theField.options[k]; k++)
          {
            if (_somethingIsSelected = (_theOption.selected && DOM.getOptionValue(_theOption)) ) { break; }
          }
        }

        if ( !(this._reqPassed[_theField.name] = _theField.value || _somethingIsSelected ) && _theField.isRequired)
        {
          _errorReqAlert += this.bullet + this.getFieldLabel(_theField, _containerElm) + "\n";
          DOM.addClass(_theField.avParent, this.reqErrorClass);
          _firstErrorField = _firstErrorField || _theField;
          _errorFields.push(_theField);
        }

      }
      // Validate checkbox and radio-button groups
      else if ( (_tagName == "input") && "checkbox|radio".indexOf(_theField.type) > -1 )
      {

        if (_theField.isRequired)
        {
          var _groupName = _theField.name;
          if (!_beenHere[_groupName])
          {
            this._purgeErrorMessages(_theField);
            DOM.addClass(_theField.avParent, this.reqErrorClass);  // Start by assuming that a checkbox/radio group is unchecked
            _errorReqAlert += "-AV-" + _theField.id + "-AV-\n";
            _beenHere[_groupName] = [0, _theField, 0, "", _errorFields.length]; // Insert a "checked" counter and representative field object for that group plus a "number of group-items" counter, plus an Error Message, plus an index to this item in the _errorFields
            _firstErrorField = _firstErrorField || _theField;
            _errorFields.push(_theField);
          }

          var _groupData = _beenHere[_groupName];
          if (_theField.checked)
          {
            DOM.removeClass(_theField.avParent, this.reqErrorClass);
            _groupData[0]++; // Increment "checked" counter
            if (_firstErrorField == _groupData[1]) { _firstErrorField = null; }
            _errorFields[_groupData[4]] = null;
          }
          _groupData[3] = _groupData[3] || _theField.errorMsg;  // assign the first _theField.errorMsg you come across  // hmmm... where would this error message come from???
          _groupData[2]++; // Increment "number of group-items" counter
        }
        if (_theField.checked) { this._reqPassed[_theField.name] = true; }

      }


      // Check for wrongly typed-in values.
      if (_theField.value || _somethingIsSelected)
      {
        var isValid = true;
        if (_theField.avCustomTypeCheck) { isValid = _theField.avCustomTypeCheck(_theField); }
        else if (_theField.avCheck.isValid) { isValid = _theField.avCheck.isValid(_theField); }

        if (!isValid)
        {
          var _theTypeInfo = _theField.errorMsg;
          if (_theTypeInfo) { _theTypeInfo = " (" + _theTypeInfo + ")"; }
          _errorTypeAlert += this.bullet + this.getFieldLabel(_theField, _containerElm) + _theTypeInfo + "\n";
          DOM.addClass(_theField.avParent, this.typeErrorClass);
          _firstErrorField = _firstErrorField || _theField;
          _errorFields.push(_theField);
        }
      }


    }




    // After-the-fact (after-the-loop) Insertion of error messages for required checkbox and radio-button groups
    for (_groupName in _beenHere)
    {
      var _groupData = _beenHere[_groupName];
      var _theMessageText = "";
      if (_groupData[0] <= 0) // if none are checked
      {
        _theMessageText = this.bullet + this.getFieldLabel(_groupData[1], _containerElm, (_groupData[2] > 1) );
        if (_groupData[3]) { _theMessageText += " ("+_groupData[3]+")" }  // hmmm... where would this error message come from???
        _theMessageText += "\n";
      }
      var _replacementLine = new RegExp("-AV-"+ _groupData[1].id +"-AV-\\n");
      _errorReqAlert = _errorReqAlert.replace(_replacementLine, _theMessageText);
    }
    // and finally clean up the _errorFields (remove any null values)
    var _tempFieldList = [];
    for (var f=0, len=_errorFields.length; f<len; f++)
    {
      if (_errorFields[f]) { _tempFieldList.push(_errorFields[f]); }
    }
    _errorFields = _tempFieldList;





    // See if there are any errors in the form.
    if (_errorFields.length && !_returnSilently)
    {

      // spit out the inline error messages
      if (this.errorMsgType != "alertonly")
      {
        this._injectInlineErrors(_errorFields);
      }


      // print out an Alert dialog to the user
      if (this.errorMsgType != "inlineonly")
      {
        // TODO: Move the Alert Error Message compilation to inside this block.
        var _alertErrorMessage = "";
        _alertErrorMessage += (_errorReqAlert)  ? this[_containerElm.avLang].errorReqMsg  + _errorReqAlert +"\n\n" : "";
        _alertErrorMessage += (_errorTypeAlert) ? this[_containerElm.avLang].errorTypeMsg + _errorTypeAlert        : "";

        if (_alertErrorMessage) {
          alert(_alertErrorMessage);
        }
      }


      // Send the browser focus to the _firstErrorField
      switch (this.errorAction)
      {
        case "focus" :
          if (this.errorMsgType == "alertonly")
          {
            // _injectInlineErrors has not been run so there's no need for further reading - just focus on the element.
            if (_firstErrorField.id)
            {
              setTimeout("DOM.$('"+_firstErrorField.id+"').focus()", 200);
            }
            else
            {
              _firstErrorField.focus();  // there's no id to supply the setTimeout with
            }
          }
          else
          {
            // create a "focus" link.
            var _focusElm = document.createElement("a");
            _focusElm.className = this.focusElmClass;
            _focusElm.href = "#";
            _focusElm.innerHTML = "#";
            DOM.prependChild(_focusElm, _firstErrorField._errMsgElm);
            DOM.aquireId(_focusElm);

            EEvent.add(_focusElm, "click", function(e) { return false; } );
            EEvent.add(_focusElm, "blur", function(e) { setTimeout("DOM.removeNode(DOM.$('"+_focusElm.id+"'))", 10); return true; } );

            // setTimeout("DOM.get('a', DOM.$('"+DOM.aquireId(_firstErrorField)+"')._errMsgElm )[0].focus()", 200);
            DOM.get('a', _firstErrorField._errMsgElm)[0].focus();
          }
          break;

        case "anchor" :
          setTimeout("document.location.href = '#" +_firstErrorField.avParent.id+ "';", 200);
          break;

      }

      return false;
    }


    return true;
  },






  cleanLabelString : function(txt)
  {
    if (!txt) { return ""; }
    txt = txt.replace(/\s\s+/g, " ");     // collapse multiple spaces
    txt = txt.replace(/ - /g, ", ");      // change all " - " seperators into commas
    txt = txt.replace(/\[/g, "(");        // change all [ into ( to eliminate confusion
    txt = txt.replace(/\]/g, ")");        // change all ] into ) to eliminate confusion
    txt = txt.replace(/\([^)]+\)/g, "");  // remove all parenthesis and their content
    txt = txt.replace(/[\s*:#]+$/, "");   // remove all spaces, asterixes, and colons from end of line
    txt = txt.replace(/^[\s*#]+/, "");    // remove all spaces and asterixes from beginning of line

    // enforce maxlength
    if (txt.length > (this.maxLabelLength+1))
    {
      txt = txt.substr(0,(this.maxLabelLength-1));
      txt = txt.replace(/[.,:;\s]+$/, "");   // chop off trailing spaces and punctuation marks
      txt += "...";
    }
    return txt;
  },




  getFieldLabel : function(_theField, _containerElm, _isGroup)
  {
    if (!_theField._avLabelText)  // do this only once
    {
      _containerElm = _containerElm || _theField.form;
      _isGroup = _isGroup || false;
      // by default use _theField.title as "label"
      var _label = (_theField.title) ? this.cleanLabelString(_theField.title) : "";

      // If no _theField.title was found, then fall back to <label>
      if (!_label && _theField.labelElm)
      {
        _label = DOM.innerText(_theField.labelElm);
        _label = this.cleanLabelString(_label);
      }
      _label = _label || _theField.name; //


      // compile a list of all ancestor fieldset "legends"  ("legend" == the first non-link element within a <fieldset>)
      var _legends = "";
      var _fieldCont = _theField.parentNode;  // the field's containing element
      var _isFirstLegend = true;

      var _spaceAndDashAtEnd = / - $/;
      var _allNewLines = /\n/g;

      while (_fieldCont != _containerElm)
      {
        if (_fieldCont.tagName.lc() == "fieldset")
        {

          var _node;
          for (var i = 0; _node = _fieldCont.childNodes[i]; i++)
          {
            if (_node.nodeType == 1  &&  DOM.innerText(_node)  &&  DOM.innerText(_node)!="#") { break; }
          }

          if (_node && _node.tagName.lc() != "fieldset")
          {
            var leg = DOM.innerText(_node);
            leg = this.cleanLabelString(leg);
            _legends = leg + " - " + _legends;
            if (_isFirstLegend && _isGroup && "checkbox|radio".indexOf(_theField.type) > -1)
            {
              _label = _legends.replace(_spaceAndDashAtEnd, "");
              _legends = "";
            }
            _isFirstLegend = false;
            break;
          }
        }
        _fieldCont = _fieldCont.parentNode;
      }

      if (_legends = _legends.replace(_spaceAndDashAtEnd, ""))
      {
        _legends = " [ " + _legends + "]";
      }

      _theField._avLabelText = (_label + _legends).replace(_allNewLines, "");  // cache for future use.
    }

    return _theField._avLabelText;
  }


};






// -------------------------------------------------------------------------------------
// Single Form-field validation functions (only used by the form-validate functions above)
// -------------------------------------------------------------------------------------

autoValidate.types = {


  fi_email : {  // Returns true if valid e-mail address
    is : {
      alertMsg  : "dæmi: notandi@daemi.is",
      inlineMsg : "Vinsamlega sláðu inn löglegt netfang (dæmi: notandi@daemi.is):"
    },
    en : {
      alertMsg  : "example: user@example.com",
      inlineMsg : "Please provide a valid e-mail address (example: user@example.com):"
    },

    isValid : function(_theField)
    {
      if (_theField.value)
      {
        _theField.value = _theField.value.trim();
        if (!(/^[a-z0-9-._+]+@([a-z0-9-_]+\.)+[a-z0-9-_]{2,99}$/i).test(_theField.value))
        {
          var _texts = this[_theField.avLang] || this.en;
          _theField.errorMsg       = _texts.alertMsg;
          _theField.errorMsgInline = _texts.inlineMsg;
          return false;
        }
      }
      return true;
    },
    isRequired : null
  },



  fi_url : {  // Returns true if valid URL  (very simplistic validation)
    is : {
      alertMsg : "dæmi: http://www.eitthvad.is/"
    },
    en : {
      alertMsg : "example: http://www.example.com/"
    },

    isValid : function(_theField)
    {
      if (_theField.value)
      {
        _theField.value = _theField.value.trim();
        var _urlPattern = /^[a-z]+:\/\/.+\..+$/;
        var _badChars = /[\(\)\<\>\,\:\"\[\]\\]/;
        var _strippedURL = _theField.value.replace(/^[a-z]+:\/\/.+$/i, "");
        if ( !_urlPattern.test(_theField.value) || _badChars.test(_strippedURL))
        {
          _theField.errorMsg = this[_theField.avLang].alertMsg || this.en.alertMsg;
          return false;
        }
      }
      return true;
    }
  },



  fi_tel : {  // Returns true if valid telephone number (further development needed)
    isValid : function(_theField)
    {
      if (_theField.value)
      {
        // This function simply removes all *legal* characters from the string
        // and then returns false if there are any left overs afterwards.
        return !_theField.value.replace(/(\s|[-+]|\d)/g, "");
      }
      return true;
    }
  },



  fi_ccnum : {  // Returns true if valid credid card number
    isValid: function(_theField)
    {
      if (_theField.value)
      {
        var isValid = !0,
            ccNum = _theField.value.replace(/[ -]/g, ""); // Strip out the optional space|dash delimiters
         
        if (!/^(\d{16}|3[47]\d{13})$/.test(ccNum)) { return false; } // ...then make sure there are only 16 digits left (or 15 in the case of AmEx (cards starting with 34 or 37))

        //_theField.value = ccNum; // insert the cleaned up creditcard number back into the field

        var isValid,
            _checkSum = 0,
            i = ccNum.length;
        while (i-->0)
        {
          _checkSum += ccNum.charAt(i) * 1;
          i--;
          var item = ccNum.charAt(i) * 2;
          _checkSum += Math.floor(item/10) + (item%10);
        }
        isValid = ((_checkSum % 10) === 0);

        // check if field-container explicitly forbids AmEx cards...
        if (isValid && ccNum.length==15 && DOM.hasClass(_theField.avParent, this.noAmExClass))
        {
          _theField.errorMsg = this[_theField.avLang].noAmEx || this.en.noAmEx;
          isValid = !isValid;
        }
      }
      return isValid;
    },
    is: {  noAmEx: "American Express kort virka ekki."  },
    en: {  noAmEx: "American Express cards are not accepted."  },
    noAmExClass: 'no-amex'
  },



  fi_ccexp : {  // Returns true if valid credid card expiry date (further development needed to check against the current year)
    isValid : function(_theField)
    {
      if (_theField.value)
      {
        _theField.value = _theField.value.replace(/(\d\d)\s*[ -\/]?\s*(\d\d)/, "$1/$2").replace(/\s+/g, ""); // accept space and dash, and change them into "/". Then remove all spaces
        return /^(0\d|1[012])\/(\d\d)$/.test(_theField.value); // ...then test against the pattern "mm/yy"
      }
      return true;
    }
  },



  fi_kt : {  // Returns true if valid Icelandic kennitala
    isValid : function(_theField)
    {
      if (_theField.value)
      {
        var Kt = _theField.value.trim().replace(/[\s\-]/g, ""); // Allow "-" and " " as delimiting characters (strip them out).
        _theField.value = Kt;
        // ...remainder must all be numericals
        // ...exactly 10 characters long
        // ...and the last character must be 0 or 9 (note potential "Y2.1K Bug" ;-)
        if ( /\D/.test(Kt)  ||  Kt.length != 10  ||  !(/(9|0)$/).test(Kt) ) { return false; }   

        var x = [3,2,7,6,5,4,3,2,1], // Checksum validation
            _summa = 0,
            i = 9;
        while (i--) { _summa += (x[i] * Kt.charAt(i)); }
        if (_summa % 11) { return false; }
      }
      return true;
    }
  },



  fi_qty : {  // Returns true only on a positive integer value.
    isValid : function(_theField)
    {
      var v = _theField.value = _theField.value.trim();
      return !v || /^\d+$/.test(v);
    }
  },


  fi_num : { // Returns true only on any numeric value (floats, negative values, etc.).
    isValid : function(_theField)
    {
      var v = _theField.value.trim().replace(/^-\s+/, '-').replace(/[,.]$/, '');
      _theField.value = v;
      return !v || (/\d/.test(v) && /^-?\d*[.,]?\d*$/.test(v));
    }
  },


  fi_percent: {
    isValid : function(_theField)
    {
      if (autoValidate.types.fi_num.isValid(_theField))
      {
        var v = parseFloat(_theField.value);
        if (v>=0  && v <= 100)
        {
          return true;
        }
        _theField.errorMsg = this[_theField.avLang] || this.en;
      }
      return false;
    },
    is: "Sláðu inn gildi á bilinu 0 til 100.",
    en: "Enter a value between 0 and 100."
  },


  fi_date : {  // Returns true on a valid date (dd.mm.yyyy|d/m/yy|etc.). Valid year-range: 1900-2099. one or two digit days and months, two or four digit years.
    is : "dæmi: %format",
    en : "example: %format",

    isValid : function(_theField)
    {
      ;;;//console.log("doing ", _theField);
      if (!_theField.value) { return true;}  // return early if field is invalid or empty

      var _cand = _theField.value.trim();
      _theField.value = _cand;

      if (!_cand) { return true;}  // return early if field is empty

      var _errorMsg = this[_theField.avLang] || this.en,
          _formatExample = "27.5.2006",
          _isValid = true;


      if (window.datePicker  &&  datePicker.VERSION<2)
      {
        var _id = _theField.id,
            _dateUI = datePicker.fields[_id];
        if (_dateUI)
        {
          var _parsedDate = datePicker.parseDate(_id);
          if (!_parsedDate)
          {
            _isValid = false;
          }
          else
          {
            var _dateStr = datePicker.printDateValue(_parsedDate, _dateUI.dateFormat, _dateUI.lang).replace(/(^\s+|\s+$)/g,"");
            if (!_dateUI.caseSensitive)
            {
              _cand = _cand.lc();
              _dateStr = _dateStr.lc();
            }
            _isValid = (_dateStr == _cand);
            if (!_isValid)
            {
              _formatExample = datePicker.printDateValue(new Date(2006,4,27), _dateUI.dateFormat, _dateUI.lang);
            }
          }
        }
      }
      else
      {
        _cand = _cand.replace(/[ .-\/]+/g, ".");
        _cand = _cand.replace(/\.(\d\d)$/, ".20$1");
        _theField.value = _cand;
        _isValid = /^(3[01]|[12]?[0-9]|(0)?[1-9])\.(1[012]|(0)?[1-9])\.(19|20)?\d\d$/.test(_cand);
      }

      if (!_isValid)
      {
        _theField.errorMsg = _errorMsg.replace(/%format/, _formatExample);
        return false;
      }
      return true;
    }
  },



  fi_year : {  // Returns true on a four-digit year. Valid range: 1900-2099.
    is : "dæmi: 1998",
    en : "example: 1998",

    isValid : function(_theField)
    {
      if (_theField.value)
      {
        _theField.value = _theField.value.trim();
        if (!/^(19|20)\d\d$/.test(_theField.value) )
        {
          _theField.errorMsg = this[_theField.avLang] || this.en;
          return false;
        }
      }
      return true;
    }
  },



  fi_postal_is : {  // Returns true on a valid Icelandic Zip-code ("póstnúmer").
    is : "dæmi: 101",
    en : "example: 101",

    isValid : function(_theField)
    {
      var _val = _theField.value,
          _islPnr = autoValidate.postCodes;
      _islPnr = _islPnr && _islPnr.is;
      if (_val)
      {
        _val = _val.trim();
        _theField.value = _val;
        var _pnrOk = false;
        if (!_islPnr)
        {
          // Póstnúmera listinn ekki til staðar. Fallback í einfalt tékk (þrír tölustafir í röð == póstnúmer)
          _pnrOk = /^\d\d\d$/.test(_val);
        }
        else if (_islPnr[_val])
        {
          var _pnrOk = true;
          var allFields = DOM.get("input", _theField.form);
          var i = allFields.length - 2;
          if ((i+1) > 0) { do
          {
            var nextField = allFields[i+1];
            // alert(nextField.className);
            if ( (allFields[i] == _theField) && DOM.hasClass(nextField, "ff_baer") )
            {
              nextField.value = _islPnr[_val];
            }
          } while (i--); }
        }
        if (!_pnrOk)
        {
          _theField.errorMsg = this[_theField.avLang] || this.en;
          return false;
        }
      }
      return true;
    }
  },



  fi_pnrs : {  // Returns true on a comma|space|semicolon deliminated list of valid Icelandic Zip-codes ("póstnúmer").
    is : "dæmi: 101, 107, 105",
    en : "example: 101, 107, 105",
    delimiter : ", ",
    isValid : function(_theField)
    {
      if (_theField.value)
      {
        var pnrstr = _theField.value.trim().replace(/([ ,;]+)/g, this.delimiter),
            cands = pnrstr.split(this.delimiter),
            i = cands.length,
            _fakeField = { avLang :_theField.avLang };

        while (i--)
        {
          _fakeField.value = cands[i];
          if (!autoValidate.types.fi_postal_is.isValid(_fakeField)) {
            _theField.errorMsg = _fakeField.errorMsg;
            return false;
          }
        }
      }
      return true;
    }
  },



  fi_time : {

    config : {
      delimeter : ':',
      ampm : false,  //  Allowed values: false (default), true (am/pm), "upper" (AM/PM)
      amDelimeter : ' ',
      leadingZero : true,
      seconds : "" // Allowed values: "add", "strip", "round", "" (default)
    },

    fieldcfg : { // hash to store field-specific configs.
      // fieldName : {
      //   delimeter : '.',
      //   ampm : true,
      //   amDelimeter : ' ',
      //   leadingZero : false,
      //   seconds : true
      // }
    },

    isValid : function(_theField)
    {
      var _val = _theField.value,
          _isOk = true;
      if (_val)
      {
        var _cfg = {};
        Object.merge(_cfg, this.config);  // fill the _cfg with default values
        Object.merge(_cfg, this.fieldcfg[_theField.name] || {});  // then add any custom (field-name specific) config values

        // Step 1: prepare _val:
        //   * normalize all spaces and punctuation marks into ":"
        //   * append a leading zero for the hours.
        _val = _val.trim().replace(/[^0-9a-z]+/gi, ':').replace(/^(\d:)/, '0$1');
        if (_cfg.ampm)
        {
          // uppercase the AM/PM and insert a leading space. Also add a default "AM" value
          _val = _val.lc().replace(/:?([ap])m?$/, ' $1m').replace(/(\d)$/, '$1 am');
          // check for validity
          _isOk = /^(1[012]|0\d)(:[0-5]\d){1,2}\s[ap]m$/.test(_val);  // Allows 00:00 am as well as 12:00 am
        }
        else
        {
          // check for validity
          _isOk = /^(2[0123]|[01]\d)(:[0-5]\d){1,2}$/.test(_val);
        }
        if (_isOk)
        {
          switch (_cfg.seconds)
          {
            case 'add':   _val = _val.replace(/^(\d+:\d+) /, '$1:00 '); break;
            case 'strip': _val = _val.replace(/^(\d+:\d+):\d+/, '$1'); break;
            case 'round': // TODO ...
          }
          if (!_cfg.leadingZero) { _val = _val.replace(/^0/, ''); }
          if (_cfg.ampm == "upper") { _val = _val.uc(); }
          _theField.value = _val.replace(/:/g, _cfg.delimeter).replace(/ /, _cfg.amDelimeter);
        }
      }
      return _isOk;
    }

  }


};


(function(){
 var t = autoValidate.types;
  // type-aliases
  t.fi_digits = t.fi_qty;
  t.fi_amount = t.fi_num;

  // Support for legacy type-names
  t.fi_dmy = t.fi_date;
  t.fi_pnr = t.fi_postal_is;
})();


