text: /**
  * jQuery Form Validator
  * ------------------------------------------
  * Created by Victor Jonsson <http://www.victorjonsson.se>
  *
  * @website http://formvalidator.net/
  * @license Dual licensed under the MIT or GPL Version 2 licenses
  * @version 2.2.beta.13
  */
  (function($) {
  
      'use strict';
  
      var $window = $(window),
          _applyErrorStyle = function($elem, conf) {
              var $parent = $elem.parent();
              $elem
                  .addClass(conf.errorElementClass)
                  .removeClass('valid');
              if($parent.hasClass("input-group")) $parent = $parent.parent();
              
              $parent.addClass('has-error') .removeClass('has-success'); //twitter bs 
  
              if(conf.borderColorOnError !== '') {
                  $elem.css('border-color', conf.borderColorOnError);
              }
          },
          _removeErrorStyle = function($elem, conf) {
              $elem.each(function() {
                  _setInlineErrorMessage($(this), '', conf, conf.errorMessagePosition);
                  var $parent = $(this).parent();
                  
                  if($parent.hasClass("input-group")) $parent = $parent.parent();
                  $(this)
                      .removeClass('valid')
                      .removeClass(conf.errorElementClass)
                      .css('border-color', '');
                  $parent
                      .removeClass('has-error')
                      .removeClass('has-success')
                      .find('.'+conf.errorMessageClass) // remove inline error message
                          .remove();
              });
          },
          _setInlineErrorMessage = function($input, mess, conf, $messageContainer) {
              var custom = _getInlineErrorElement($input);
              var $parent = $input.parent();
              if($parent.hasClass("input-group")) $parent = $parent.parent();
              if( custom ) {
                  custom.innerHTML = mess;
              }
              else if( typeof $messageContainer == 'object' ) {
                  var $found = false;
                  $messageContainer.find('.'+conf.errorMessageClass).each(function() {
                      if( this.inputReferer == $input[0] ) {
                          $found = $(this);
                          return false;
                      }
                  });
                  if( $found ) {
                      if( !mess ) {
                          $found.remove();
                      } else {
                          $found.html(mess);
                      }
                  } else {
                      var $mess = $('<div class="'+conf.errorMessageClass+'">'+mess+'</div>');
                      $mess[0].inputReferer = $input[0];
                      $messageContainer.prepend($mess);
                  }
              }
              else {
                  var $mess = $parent.find('.'+conf.errorMessageClass+'.help-block');
                  if( $mess.length == 0 ) {
                      $mess = $('<span></span>').addClass('help-block').addClass(conf.errorMessageClass);
                      $mess.appendTo($parent);
                  }
                  $mess.attr('data-translate', Mythos.toUrl(mess));
                  $mess.html(mess);
                  Mythos.translator.translateElement($mess, Mythos.translator.translations[Mythos.toUrl(mess)]);
              }
          },
          _getInlineErrorElement = function($input, conf) {
              return document.getElementById($input.attr('name')+'_err_msg');
          },
          _templateMessage = function($form, title, errorMessages, conf) {
              var messages = conf.errorMessageTemplate.messages.replace(/\{errorTitle\}/g, title);
              var fields = [];
              $.each(errorMessages, function(i, msg) {
                  fields.push(conf.errorMessageTemplate.field.replace(/\{msg\}/g, msg));
              });
              messages = messages.replace(/\{fields\}/g, fields.join(''));
              var container = conf.errorMessageTemplate.container.replace(/\{errorMessageClass\}/g, conf.errorMessageClass);
              container = container.replace(/\{messages\}/g, messages);
              $form.children().eq(0).before(container);
          };
  
      /**
      * Assigns validateInputOnBlur function to elements blur event
      *
      * @param {Object} language Optional, will override $.formUtils.LANG
      * @param {Object} settings Optional, will override the default settings
      * @return {jQuery}
      */
      $.fn.validateOnBlur = function(language, settings) {
          this.find('input[data-validation],textarea[data-validation],select[data-validation]')
              .bind('blur.validation', function() {
                  $(this).validateInputOnBlur(language, settings, true, 'blur');
              });
  
          return this;
      };
  
  	/*
  	 * Assigns validateInputOnBlur function to elements custom event
  	 * @param {Object} language Optional, will override $.formUtils.LANG
  	 * @param {Object} settings Optional, will override the default settings
  	 * * @return {jQuery}	
  	 */
  	$.fn.validateOnEvent = function(language, settings) {
          this.find('input[data-validation][data-validation-event],textarea[data-validation][data-validation-event],select[data-validation][data-validation-event]')
  			.each(function(){
  				var $el = $(this),
  				    etype = $el.attr("data-validation-event");
  				if (etype){
  					$el.bind(etype + ".validation", function(){
                  		$(this).validateInputOnBlur(language, settings, true, etype);
  					});
  				}
  			});
  		return this;
  	};
  
      /**
      * fade in help message when input gains focus
      * fade out when input loses focus
      * <input data-help="The info that I want to display for the user when input is focused" ... />
      *
      * @param {String} attrName - Optional, default is data-help
      * @return {jQuery}
      */
      $.fn.showHelpOnFocus = function(attrName) {
          if(!attrName) {
              attrName = 'data-validation-help';
          }
  
          // Remove previously added event listeners
          this.find('.has-help-txt')
                  .valAttr('has-keyup-event', false)
                  .removeClass('has-help-txt');
  
          // Add help text listeners
          this.find('textarea,input').each(function() {
              var $elem = $(this),
                  className = 'jquery_form_help_' + ($elem.attr('name') || '').replace( /(:|\.|\[|\])/g, "" ),
                  help = $elem.attr(attrName);
  
              if(help) {
                  $elem
                      .addClass('has-help-txt')
                      .unbind('focus.help')
                      .bind('focus.help', function() {
                          var $help = $elem.parent().find('.'+className);
                          if($help.length == 0) {
                              $help = $('<span />')
                                          .addClass(className)
                                          .addClass('help')
                                          .addClass('help-block') // twitter bs
                                          .text(help)
                                          .hide();
  
                              $elem.after($help);
  
                          }
                          $help.fadeIn();
                      })
                      .unbind('blur.help')
                      .bind('blur.help', function() {
                          $(this)
                              .parent()
                              .find('.'+className)
                                  .fadeOut('slow');
                      });
              }
          });
  
          return this;
      };
  
      /**
      * Validate single input when it loses focus
      * shows error message in a span element 
      * that is appended to the parent element
      *
      * @param {Object} [language] Optional, will override $.formUtils.LANG
      * @param {Object} [conf] Optional, will override the default settings
      * @param {Boolean} attachKeyupEvent Optional
      * @param {String} eventType
      * @return {jQuery}
      */
      $.fn.validateInputOnBlur = function(language, conf, attachKeyupEvent, eventType) {
          $.formUtils.eventType = eventType;
  
          if( (this.valAttr('suggestion-nr') || this.valAttr('postpone') || this.hasClass('hasDatepicker')) && !window.postponedValidation ) {
              // This validation has to be postponed
              var _self = this,
                  postponeTime = this.valAttr('postpone') || 200;
  
              window.postponedValidation = function() {
                  _self.validateInputOnBlur(language, conf, attachKeyupEvent, eventType);
                  window.postponedValidation = false;
              };
              setTimeout(function() {
                  if( window.postponedValidation ) {
                      window.postponedValidation();
                  }
              }, postponeTime);
  
              return this;
          }
  
          language = $.extend({}, $.formUtils.LANG, language || {});
          _removeErrorStyle(this, conf);
          var $elem = this,
              $form = $elem.closest("form"),
              validationRule = $elem.attr(conf.validationRuleAttribute),
              validation = $.formUtils.validateInput(
                              $elem,
                              language,
                              $.extend({}, conf, {errorMessagePosition:'element'}),
                              $form,
                              eventType
                          );
          
          if(validation === true) {
              $elem
                  .addClass('valid')
                  .parent()
                      .addClass('has-success'); // twitter bs
          } else if(validation !== null) {
  
              _applyErrorStyle($elem, conf);
              _setInlineErrorMessage($elem, validation, conf, conf.errorMessagePosition);
  
              if(attachKeyupEvent) {
                  $elem
                      .unbind('keyup.validation')
                      .bind('keyup.validation', function() {
                          $(this).validateInputOnBlur(language, conf, false, 'keyup');
                      });
              }
          }
  
          return this;
      };
  
      /**
       * Short hand for fetching/adding/removing element attributes
       * prefixed with 'data-validation-'
       *
       * @param {String} name
       * @param {String|Boolean} [val]
       * @return string|undefined
       * @protected
       */
      $.fn.valAttr = function(name, val) {
          if( val === undefined ) {
              return this.attr('data-validation-'+name);
          } else if( val === false || val === null ) {
              return this.removeAttr('data-validation-'+name);
          } else {
              if(name.length > 0) name='-'+name;
              return this.attr('data-validation'+name, val);
          }
      };
  
      /**
       * Function that validate all inputs in given element
       *
       * @param {Object} [language]
       * @param {Object} [conf]
       * @param {Boolean} [displayError] Defaults to true
       */
      $.fn.isValid = function(language, conf, displayError) {
  
          if ($.formUtils.isLoadingModules) {
              var $self = this;
              setTimeout(function () {
                  $self.isValid(language, conf, displayError);
              }, 200);
              return null;
          }
  
          conf = $.extend({}, $.formUtils.defaultConfig(), conf || {});
          language = $.extend({}, $.formUtils.LANG, language || {});
          displayError = displayError !== false;
  
          $.formUtils.isValidatingEntireForm = true;
          $.formUtils.haltValidation = false;
  
          /**
           * Adds message to error message stack if not already in the message stack
           *
           * @param {String} mess
           * @para {jQuery} $elem
           */
          var addErrorMessage = function(mess, $elem) {
              // validate server side will return null as error message before the server is requested
              if(mess !== null) {
                  if ($.inArray(mess, errorMessages) < 0) {
                      errorMessages.push(mess);
                  }
                  errorInputs.push($elem);
                  $elem.attr('current-error', mess);
                  if( displayError )
                      _applyErrorStyle($elem, conf);
              }
          },
  
          /** Error messages for this validation */
          errorMessages = [],
  
          /** Input elements which value was not valid */
          errorInputs = [],
  
          /** Form instance */
          $form = this,
  
          /**
           * Tells whether or not to validate element with this name and of this type
           *
           * @param {String} name
           * @param {String} type
           * @return {Boolean}
           */
          ignoreInput = function(name, type) {
              if (type === 'submit' || type === 'button' || type == 'reset') {
                  return true;
              }
              return $.inArray(name, conf.ignore || []) > -1;
          };
  
          // Reset style and remove error class
          if( displayError ) {
              $form.find('.'+conf.errorMessageClass+'.alert').remove();
              _removeErrorStyle($form.find('.'+conf.errorElementClass+',.valid'), conf);
          }
  
          // Validate element values
          $form.find('input,textarea,select').filter(':not([type="submit"],[type="button"])').each(function() {
              var $elem = $(this);
              var elementType = $elem.attr('type');
              if (!ignoreInput($elem.attr('name'), elementType)) {
  
                  var validation = $.formUtils.validateInput(
                                  $elem,
                                  language,
                                  conf,
                                  $form,
                                  'submit'
                              );
  
                  // Run element validation callback
                  if( typeof conf.onElementValidate == 'function' ) {
                      conf.onElementValidate((validation === true), $elem, $form, validation);
                  }
  
                  if(validation !== true) {
                      addErrorMessage(validation, $elem);
                  } else {
                      $elem
                          .valAttr('current-error', false)
                          .addClass('valid')
                          .parent()
                              .addClass('has-success');
                  }
              }
  
          });
  
          // Run validation callback
          if( typeof conf.onValidate == 'function' ) {
              var errors = conf.onValidate($form);
              if( $.isArray(errors) ) {
                  $.each(errors, function(i, err) {
                      addErrorMessage(err.message, err.element);
                  });
              }
              else if( errors && errors.element && errors.message ) {
                  addErrorMessage(errors.message, errors.element);
              }
          }
  
          // Reset form validation flag
          $.formUtils.isValidatingEntireForm = false;
  
          // Validation failed
          if ( !$.formUtils.haltValidation && errorInputs.length > 0 ) {
  
              if( displayError ) {
                  // display all error messages in top of form
                  if (conf.errorMessagePosition === 'top') {
                      _templateMessage($form, language.errorTitle, errorMessages, conf);
                  }
                  // Customize display message
                  else if(conf.errorMessagePosition === 'custom') {
                      if( typeof conf.errorMessageCustom === 'function' ) {
                          conf.errorMessageCustom($form, language.errorTitle, errorMessages, conf);
                      }
                  }
                  // Display error message below input field or in defined container
                  else  {
                      $.each(errorInputs, function(i, $input) {
                          _setInlineErrorMessage($input, $input.attr('current-error'), conf, conf.errorMessagePosition);
                      });
                  }
  
                  if(conf.scrollToTopOnError) {
                      $window.scrollTop($form.offset().top - 20);
                  }
              }
  
              return false;
          }
  
          return !$.formUtils.haltValidation;
      };
  
      /**
       * @deprecated
       * @param language
       * @param conf
       */
      $.fn.validateForm = function(language, conf) {
          if( window.console && typeof window.console.warn == 'function' ) {
              window.console.warn('Use of deprecated function $.validateForm, use $.isValid instead');
          }
          return this.isValid(language, conf, true);
      }
  
      /**
      * Plugin for displaying input length restriction
      */
      $.fn.restrictLength = function(maxLengthElement) {
          new $.formUtils.lengthRestriction(this, maxLengthElement);
          return this;
      };
  
      /**
       * Add suggestion dropdown to inputs having data-suggestions with a comma
       * separated string with suggestions
       * @param {Array} [settings]
       * @returns {jQuery}
       */
      $.fn.addSuggestions = function(settings) {
          var sugs = false;
          this.find('input').each(function() {
              var $field = $(this);
  
              sugs = $.split($field.attr('data-suggestions'));
  
              if( sugs.length > 0 && !$field.hasClass('has-suggestions') ) {
                  $.formUtils.suggest($field, sugs, settings);
                  $field.addClass('has-suggestions');
              }
          });
          return this;
      };
  
      /**
       * A bit smarter split function
       * @param {String} val
       * @param {Function|String} [func]
       * @param {String} [delim]
       * @returns {Array|void}
       */
      $.split = function(val, func, delim) {
          if( typeof func != 'function' ) {
              // return string
              if( !val )
                  return [];
              var values = [];
              $.each(val.split(func ? func:','), function(i,str) {
                  str = $.trim(str);
                  if( str.length )
                      values.push(str);
              });
              return values;
          } else if( val ) {
              // use callback on each
              if( !delim )
                  delim = ',';
              $.each(val.split(delim), function(i, str) {
                  str = $.trim(str);
                  if( str.length )
                      return func(str, i);
              });
          }
      };
  
      /**
       * Short hand function that makes the validation setup require less code
       * @param conf
       */
      $.validate = function(conf) {
  
          var defaultConf = $.extend($.formUtils.defaultConfig(), {
              form : 'form',
  			/*
  			 * Enable custom event for validation
  			 */
              validateOnEvent : true,
              validateOnBlur : true,
              showHelpOnFocus : true,
              addSuggestions : true,
              modules : '',
              onModulesLoaded : null,
              language : false,
              onSuccess : false,
              onError : false,
              onElementValidate : false
          });
  
          conf = $.extend(defaultConf, conf || {});
  
          // Add validation to forms
          $(conf.form).each(function(i, form) {
  
              var $form  = $(form);
              $window.trigger('formValidationSetup', [$form]);
  
              // Remove all event listeners previously added
              $form.find('.has-help-txt')
                  .unbind('focus.validation')
                  .unbind('blur.validation');
              $form
                  .removeClass('has-validation-callback')
                  .unbind('submit.validation')
                  .unbind('reset.validation')
                  .find('input[data-validation],textarea[data-validation]')
                      .unbind('blur.validation');
  
              // Validate when submitted
              $form.bind('submit.validation', function() {
                  var $form = $(this);
  
                  if($.formUtils.isLoadingModules) {
                      setTimeout(function() {
                          $form.trigger('submit.validation');
                      }, 200);
                      return false;
                  }
                  var valid = $form.isValid(conf.language, conf);
                  if( valid && typeof conf.onSuccess == 'function') {
                      var callbackResponse = conf.onSuccess($form);
                      if( callbackResponse === false )
                          return false;
                  } else if ( !valid && typeof conf.onError == 'function' ) {
                      conf.onError($form);
                      return false;
                  } else {
                      return valid;
                  }
              })
              .bind('reset.validation', function() {
                  // remove messages
                  $(this).find('.'+conf.errorMessageClass+'.alert').remove();
                  _removeErrorStyle($(this).find('.'+conf.errorElementClass+',.valid'), conf);
              })
              .addClass('has-validation-callback');
  
              if( conf.showHelpOnFocus ) {
                  $form.showHelpOnFocus();
              }
              if( conf.addSuggestions ) {
                  $form.addSuggestions();
              }
              if( conf.validateOnBlur ) {
                  $form.validateOnBlur(conf.language, conf);
                  $form.bind('html5ValidationAttrsFound', function() {
                      $form.validateOnBlur(conf.language, conf);
                  })
              }
  			if( conf.validateOnEvent ){
                  $form.validateOnEvent(conf.language, conf);
  			}
  
          });
  
          if( conf.modules != '' ) {
              if( typeof conf.onModulesLoaded == 'function' ) {
                  $window.one('validatorsLoaded', conf.onModulesLoaded);
              }
              $.formUtils.loadModules(conf.modules);
          }
      };
  
      /**
       * Object containing utility methods for this plugin
       */
      $.formUtils = {
  
          /**
           * Default config for $(...).isValid();
           */
          defaultConfig :  function() {
              return {
                  ignore : [], // Names of inputs not to be validated even though node attribute containing the validation rules tells us to
                  errorElementClass : 'error', // Class that will be put on elements which value is invalid
                  borderColorOnError : 'red', // Border color of elements which value is invalid, empty string to not change border color
                  errorMessageClass : 'form-error', // class name of div containing error messages when validation fails
                  validationRuleAttribute : 'data-validation', // name of the attribute holding the validation rules
                  validationErrorMsgAttribute : 'data-validation-error-msg', // define custom err msg inline with element
                  errorMessagePosition : 'element', // Can be either "top" or "element" or "custom"
                  errorMessageTemplate : {
                      container: '<div class="{errorMessageClass} alert alert-danger">{messages}</div>',
                      messages: '<strong>{errorTitle}</strong><ul>{fields}</ul>',
                      field: '<li>{msg}</li>'
                  },
                  errorMessageCustom: _templateMessage,
                  scrollToTopOnError : true,
                  dateFormat : 'yyyy-mm-dd',
                  addValidClassOnAll : false, // whether or not to apply class="valid" even if the input wasn't validated
                  decimalSeparator : '.'
              }
          },
  
          /**
          * Available validators
          */
          validators : {},
  
          /**
           * Events triggered by form validator
           */
          _events : {load : [], valid: [], invalid:[]},
  
          /**
           * Setting this property to true during validation will
           * stop further validation from taking place and form will
           * not be sent
           */
          haltValidation : false,
  
          /**
           * This variable will be true $.fn.isValid() is called
           * and false when $.fn.validateOnBlur is called
           */
          isValidatingEntireForm : false,
  
          /**
          * Function for adding a validator
          * @param {Object} validator
          */
          addValidator : function(validator) {
              // prefix with "validate_" for backward compatibility reasons
              var name = validator.name.indexOf('validate_') === 0 ? validator.name : 'validate_'+validator.name;
              if( validator.validateOnKeyUp === undefined )
                  validator.validateOnKeyUp = true;
              this.validators[name] = validator;
          },
  
          /**
           * @var {Boolean}
           */
          isLoadingModules : false,
  
          /**
           * @var {Object}
           */
          loadedModules : {},
  
          /**
          * @example
          *  $.formUtils.loadModules('date, security.dev');
          *
          * Will load the scripts date.js and security.dev.js from the
          * directory where this script resides. If you want to load
          * the modules from another directory you can use the
          * path argument.
          *
          * The script will be cached by the browser unless the module
          * name ends with .dev
          *
          * @param {String} modules - Comma separated string with module file names (no directory nor file extension)
          * @param {String} [path] - Optional, path where the module files is located if their not in the same directory as the core modules
          * @param {Boolean} [fireEvent] - Optional, whether or not to fire event 'load' when modules finished loading
          */
          loadModules : function(modules, path, fireEvent) {
  
              if( fireEvent === undefined )
                  fireEvent = true;
  
              if( $.formUtils.isLoadingModules ) {
                  setTimeout(function() {
                      $.formUtils.loadModules(modules, path, fireEvent);
                  });
                  return;
              }
  
              var hasLoadedAnyModule = false,
                  loadModuleScripts = function(modules, path) {
  
                      var moduleList = $.split(modules),
                          numModules = moduleList.length,
                          moduleLoadedCallback = function() {
                              numModules--;
                              if( numModules == 0 ) {
                                  $.formUtils.isLoadingModules = false;
                                  if( fireEvent && hasLoadedAnyModule ) {
                                      $window.trigger('validatorsLoaded');
                                  }
                              }
                          };
  
                      if( numModules > 0 ) {
                          $.formUtils.isLoadingModules = true;
                      }
  
                      var cacheSuffix = '?__='+( new Date().getTime() ),
                          appendToElement = document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0];
  
                      $.each(moduleList, function(i, modName) {
                          modName = $.trim(modName);
                          if( modName.length == 0 ) {
                              moduleLoadedCallback();
                          }
                          else {
                              var scriptUrl = path + modName + (modName.substr(-3) == '.js' ? '':'.js'),
                                  script = document.createElement('SCRIPT');
  
                              if( scriptUrl in $.formUtils.loadedModules ) {
                                  // already loaded
                                  moduleLoadedCallback();
                              }
                              else {
  
                                  // Remember that this script is loaded
                                  $.formUtils.loadedModules[scriptUrl] = 1;
                                  hasLoadedAnyModule = true;
  
                                  // Load the script
                                  script.type = 'text/javascript';
                                  script.onload = moduleLoadedCallback;
                                  script.src = scriptUrl + ( scriptUrl.substr(-7) == '.dev.js' ? cacheSuffix:'' );
                                  script.onreadystatechange = function() {
                                      // IE 7 fix
                                      if( this.readyState == 'complete' || this.readyState == 'loaded' ) {
                                          moduleLoadedCallback();
                                          // Handle memory leak in IE
                                          this.onload = null;
                                          this.onreadystatechange = null;
                                      }
                                  };
                                  appendToElement.appendChild( script );
                              }
                          }
                      });
                  };
  
              if( path ) {
                  loadModuleScripts(modules, path);
              } else {
                  var findScriptPathAndLoadModules = function() {
                      var foundPath = false;
                      $('script[src*="form-validator"]').each(function() {
                          foundPath = this.src.substr(0, this.src.lastIndexOf('/')) + '/';
                          if( foundPath == '/' )
                              foundPath = '';
                          return false;
                      });
  
                      if( foundPath !== false) {
                          loadModuleScripts(modules, foundPath);
                          return true;
                      }
                      return false;
                  };
  
                  if( !findScriptPathAndLoadModules() ) {
                      $(findScriptPathAndLoadModules);
                  }
              }
          },
  
          /**
          * Validate the value of given element according to the validation rules
          * found in the attribute data-validation. Will return null if no validation
          * should take place, returns true if valid or error message if not valid
          *
          * @param {jQuery} $elem
          * @param {Object} language ($.formUtils.LANG)
          * @param {Object} conf
          * @param {jQuery} $form
          * @param {String} [eventContext]
          * @return {String|Boolean}
          */
          validateInput : function($elem, language, conf, $form, eventContext) {
  
              if( $elem.attr('disabled') )
                  return null; // returning null will prevent that the valid class gets applied to the element
  
              $elem.trigger('beforeValidation');
  
              var value = $elem.val() || '',
                  optional = $elem.valAttr('optional'),
  
                  // test if a checkbox forces this element to be validated
                  validationDependsOnCheckedInput = false,
                  validationDependentInputIsChecked = false,
                  validateIfCheckedElement = false,
  
                  // get value of this element's attribute "... if-checked"
                  validateIfCheckedElementName = $elem.valAttr("if-checked");
  
              // make sure we can proceed
              if (validateIfCheckedElementName != null) {
  
                  // Set the boolean telling us that the validation depends
                  // on another input being checked
                  validationDependsOnCheckedInput = true;
  
                  // select the checkbox type element in this form
                  validateIfCheckedElement = $form.find('input[name="' + validateIfCheckedElementName + '"]');
  
                  // test if it's property "checked" is checked
                  if ( validateIfCheckedElement.prop('checked') ) {
                      // set value for validation checkpoint
                      validationDependentInputIsChecked = true;
                  }
              }
  
              // validation checkpoint
              // if empty AND optional attribute is present
              // OR depending on a checkbox being checked AND checkbox is checked, return true
              if ((!value && optional === 'true') || (validationDependsOnCheckedInput && !validationDependentInputIsChecked)) {
                  return conf.addValidClassOnAll ? true:null;
              }
  
              var validationRules = $elem.attr(conf.validationRuleAttribute),
  
                  // see if form element has inline err msg attribute
                  validationErrorMsg = true;
  
              if( !validationRules ) {
                  return conf.addValidClassOnAll ? true:null;
              }
  
              $.split(validationRules, function(rule) {
                  if( rule.indexOf('validate_') !== 0 ) {
                      rule = 'validate_' + rule;
                  }
  
                  var validator = $.formUtils.validators[rule];
  
                  if( validator && typeof validator['validatorFunction'] == 'function' ) {
                      // special change of element for checkbox_group rule
                      if ( rule == 'validate_checkbox_group' ) {
                          // set element to first in group, so error msg is set only once
                              $elem = $("[name='"+$elem.attr('name')+"']:eq(0)");
                      }
  
                      var isValid = null;
                      if( eventContext != 'keyup' || validator.validateOnKeyUp ) {
                          isValid = validator.validatorFunction(value, $elem, conf, language, $form);
                      }
  
                      if(!isValid) {
                          validationErrorMsg = null;
                          if( isValid !== null ) {
                              validationErrorMsg =  $elem.attr(conf.validationErrorMsgAttribute+'-'+rule.replace('validate_', ''));
                              if( !validationErrorMsg ) {
                                  validationErrorMsg =  $elem.attr(conf.validationErrorMsgAttribute);
                                  if( !validationErrorMsg ) {
                                      validationErrorMsg = language[validator.errorMessageKey];
                                      if( !validationErrorMsg )
                                          validationErrorMsg = validator.errorMessage;
                                  }
                              }
                          }
                          return false; // breaks the iteration
                      }
  
                  } else {
                      throw new Error('Using undefined validator "'+rule+'"');
                  }
  
              }, ' ');
  
              if( typeof validationErrorMsg == 'string' ) {
                  $elem.trigger('validation', false);
                  return validationErrorMsg;
              } else if( validationErrorMsg === null && !conf.addValidClassOnAll ) {
                  return null;
              } else {
                  $elem.trigger('validation', true);
                  return true;
              }
          },
  
         /**
          * Is it a correct date according to given dateFormat. Will return false if not, otherwise
          * an array 0=>year 1=>month 2=>day
          * 
          * rewroted function by Zoltan Csontos
          * @dependency - date-format.js
          * @param {String} val
          * @param {String} dateFormat
          * @return {Array}|{Boolean}
          */
          parseDate : function(val, dateFormat) {
              var valToDate = new Date(val),
                  year = valToDate.getFullYear(),
                  month = valToDate.getMonth(),
                  day = valToDate.getDay(),
                  formated = "";
              if (valToDate != "Invalid Date" && val !== "") {
                  formated = valToDate.format(dateFormat);
                  if (formated == val) {
                      return true;
                  }
              }
              return false;
          },
  
         /**
          * skum fix. är talet 05 eller lägre ger parseInt rätt int annars får man 0 när man kör parseInt?
          *
          * @param {String} val
          * @param {Number}
          */
          parseDateInt : function(val) {
              if (val.indexOf('0') === 0) {
                  val = val.replace('0', '');
              }
              return parseInt(val,10);
          },
  
          /**
          * Has month only 30 days?
          *
          * @param {Number} m
          * @return {Boolean}
          */
          isShortMonth : function(m) {
              return (m % 2 === 0 && m < 7) || (m % 2 !== 0 && m > 7);
          },
  
         /**
          * Restrict input length
          *
          * @param {jQuery} $inputElement Jquery Html object
          * @param {jQuery} $maxLengthElement jQuery Html Object
          * @return void
          */
          lengthRestriction : function($inputElement, $maxLengthElement) {
                  // read maxChars from counter display initial text value
             var maxChars = parseInt($maxLengthElement.text(),10),
                  charsLeft = 0,
  
                 // internal function does the counting and sets display value
                 countCharacters = function() {
                     var numChars = $inputElement.val().length;
                     if(numChars > maxChars) {
                         // get current scroll bar position
                         var currScrollTopPos = $inputElement.scrollTop();
                         // trim value to max length
                         $inputElement.val($inputElement.val().substring(0, maxChars));
                         $inputElement.scrollTop(currScrollTopPos);
                     }
                     charsLeft = maxChars - numChars;
                     if( charsLeft < 0 )
                         charsLeft = 0;
  
                     // set counter text
                     $maxLengthElement.text(charsLeft);
                 };
  
             // bind events to this element
             // setTimeout is needed, cut or paste fires before val is available
             $($inputElement).bind('keydown keyup keypress focus blur',  countCharacters )
                 .bind('cut paste', function(){ setTimeout(countCharacters, 100); } ) ;
  
             // count chars on pageload, if there are prefilled input-values
             $(document).bind("ready", countCharacters);
          },
  
          /**
          * Test numeric against allowed range
          *
          * @param $value int
          * @param $rangeAllowed str; (1-2, min1, max2)
          * @return array 
          */
          numericRangeCheck : function(value, rangeAllowed) 
          {
              // split by dash
              var range = $.split(rangeAllowed, '-');
              // min or max
              var minmax = parseInt(rangeAllowed.substr(3),10)
              // range ?
              if (range.length == 2 && (value < parseInt(range[0],10) || value > parseInt(range[1],10) ) )
              {   return [ "out", range[0], range[1] ] ; } // value is out of range
              else if (rangeAllowed.indexOf('min') === 0 && (value < minmax ) ) // min
              {  return ["min", minmax]; } // value is below min
              else if (rangeAllowed.indexOf('max') === 0 && (value > minmax ) ) // max
              {   return ["max", minmax]; } // value is above max
              // since no other returns executed, value is in allowed range
              return [ "ok" ] ; 
          },
  
  
          _numSuggestionElements : 0,
          _selectedSuggestion : null,
          _previousTypedVal : null,
  
          /**
           * Utility function that can be used to create plugins that gives
           * suggestions when inputs is typed into
           * @param {jQuery} $elem
           * @param {Array} suggestions
           * @param {Object} settings - Optional
           * @return {jQuery}
           */
          suggest : function($elem, suggestions, settings) {
              var conf =  {
                  css : {
                      maxHeight: '150px',
                      background: '#FFF',
                      lineHeight:'150%',
                      textDecoration : 'underline',
                      overflowX : 'hidden',
                      overflowY : 'auto',
                      border : '#CCC solid 1px',
                      borderTop : 'none',
                      cursor: 'pointer'
                  },
                  activeSuggestionCSS : {
                      background : '#E9E9E9'
                  }
              },
              setSuggsetionPosition = function($suggestionContainer, $input) {
                  var offset = $input.offset();
                  $suggestionContainer.css({
                      width : $input.outerWidth(),
                      left : offset.left + 'px',
                      top : (offset.top + $input.outerHeight()) +'px'
                  });
              };
  
              if(settings)
                  $.extend(conf, settings);
  
              conf.css['position'] = 'absolute';
              conf.css['z-index'] = 9999;
              $elem.attr('autocomplete', 'off');
  
              if( this._numSuggestionElements === 0 ) {
                  // Re-position suggestion container if window size changes
                  $window.bind('resize', function() {
                      $('.jquery-form-suggestions').each(function() {
                          var $container = $(this),
                              suggestID = $container.attr('data-suggest-container');
                          setSuggsetionPosition($container, $('.suggestions-'+suggestID).eq(0));
                      });
                  });
              }
  
              this._numSuggestionElements++;
  
              var onSelectSuggestion = function($el) {
                  var suggestionId = $el.valAttr('suggestion-nr');
                  $.formUtils._selectedSuggestion = null;
                  $.formUtils._previousTypedVal = null;
                  $('.jquery-form-suggestion-'+suggestionId).fadeOut('fast');
              };
  
              $elem
                  .data('suggestions', suggestions)
                  .valAttr('suggestion-nr', this._numSuggestionElements)
                  .unbind('focus.suggest')
                  .bind('focus.suggest', function() {
                      $(this).trigger('keyup');
                      $.formUtils._selectedSuggestion = null;
                  })
                  .unbind('keyup.suggest')
                  .bind('keyup.suggest', function() {
                      var $input = $(this),
                          foundSuggestions = [],
                          val = $.trim($input.val()).toLocaleLowerCase();
  
                      if(val == $.formUtils._previousTypedVal) {
                          return;
                      }
                      else {
                          $.formUtils._previousTypedVal = val;
                      }
  
                      var hasTypedSuggestion = false,
                          suggestionId = $input.valAttr('suggestion-nr'),
                          $suggestionContainer = $('.jquery-form-suggestion-'+suggestionId);
  
                      $suggestionContainer.scrollTop(0);
  
                      // Find the right suggestions
                      if(val != '') {
                          var findPartial = val.length > 2;
                          $.each($input.data('suggestions'), function(i, suggestion) {
                              var lowerCaseVal = suggestion.toLocaleLowerCase();
                              if( lowerCaseVal == val ) {
                                  foundSuggestions.push('<strong>'+suggestion+'</strong>');
                                  hasTypedSuggestion = true;
                                  return false;
                              } else if(lowerCaseVal.indexOf(val) === 0 || (findPartial && lowerCaseVal.indexOf(val) > -1)) {
                                  foundSuggestions.push(suggestion.replace(new RegExp(val, 'gi'), '<strong>$&</strong>'));
                              }
                          });
                      }
  
                      // Hide suggestion container
                      if(hasTypedSuggestion || (foundSuggestions.length == 0 && $suggestionContainer.length > 0)) {
                          $suggestionContainer.hide();
                      }
  
                      // Create suggestion container if not already exists
                      else if(foundSuggestions.length > 0 && $suggestionContainer.length == 0) {
                          $suggestionContainer = $('<div></div>').css(conf.css).appendTo('body');
                          $elem.addClass('suggestions-'+suggestionId);
                          $suggestionContainer
                              .attr('data-suggest-container', suggestionId)
                              .addClass('jquery-form-suggestions')
                              .addClass('jquery-form-suggestion-'+suggestionId);
                      }
  
                      // Show hidden container
                      else if(foundSuggestions.length > 0 && !$suggestionContainer.is(':visible')) {
                          $suggestionContainer.show();
                      }
  
                      // add suggestions
                      if(foundSuggestions.length > 0 && val.length != foundSuggestions[0].length) {
  
                          // put container in place every time, just in case
                          setSuggsetionPosition($suggestionContainer, $input);
  
                          // Add suggestions HTML to container
                          $suggestionContainer.html('');
                          $.each(foundSuggestions, function(i, text) {
                              $('<div></div>')
                                  .append(text)
                                  .css({
                                      overflow: 'hidden',
                                      textOverflow : 'ellipsis',
                                      whiteSpace : 'nowrap',
                                      padding: '5px'
                                  })
                                  .addClass('form-suggest-element')
                                  .appendTo($suggestionContainer)
                                  .click(function() {
                                      $input.focus();
                                      $input.val( $(this).text() );
                                      onSelectSuggestion($input);
                                  });
                          });
                      }
                  })
                  .unbind('keydown.validation')
                  .bind('keydown.validation', function(e) {
                      var code = (e.keyCode ? e.keyCode : e.which),
                          suggestionId,
                          $suggestionContainer,
                          $input = $(this);
  
                      if(code == 13 && $.formUtils._selectedSuggestion !== null) {
                          suggestionId = $input.valAttr('suggestion-nr');
                          $suggestionContainer = $('.jquery-form-suggestion-'+suggestionId);
                          if($suggestionContainer.length > 0) {
                              var newText = $suggestionContainer.find('div').eq($.formUtils._selectedSuggestion).text();
                              $input.val(newText);
                              onSelectSuggestion($input);
                              e.preventDefault();
                          }
                      }
                      else {
                          suggestionId = $input.valAttr('suggestion-nr');
                          $suggestionContainer = $('.jquery-form-suggestion-'+suggestionId);
                          var $suggestions = $suggestionContainer.children();
                          if($suggestions.length > 0 && $.inArray(code, [38,40]) > -1) {
                              if(code == 38) { // key up
                                  if($.formUtils._selectedSuggestion === null)
                                      $.formUtils._selectedSuggestion = $suggestions.length-1;
                                  else
                                      $.formUtils._selectedSuggestion--;
                                  if($.formUtils._selectedSuggestion < 0)
                                      $.formUtils._selectedSuggestion = $suggestions.length-1;
                              }
                              else if(code == 40) { // key down
                                  if($.formUtils._selectedSuggestion === null)
                                      $.formUtils._selectedSuggestion = 0;
                                  else
                                      $.formUtils._selectedSuggestion++;
                                  if($.formUtils._selectedSuggestion > ($suggestions.length-1))
                                      $.formUtils._selectedSuggestion = 0;
  
                              }
  
                              // Scroll in suggestion window
                              var containerInnerHeight = $suggestionContainer.innerHeight(),
                                  containerScrollTop = $suggestionContainer.scrollTop(),
                                  suggestionHeight = $suggestionContainer.children().eq(0).outerHeight(),
                                  activeSuggestionPosY = suggestionHeight * ($.formUtils._selectedSuggestion);
  
                              if( activeSuggestionPosY < containerScrollTop || activeSuggestionPosY > (containerScrollTop+containerInnerHeight)) {
                                  $suggestionContainer.scrollTop( activeSuggestionPosY );
                              }
  
                              $suggestions
                                  .removeClass('active-suggestion')
                                  .css('background', 'none')
                                  .eq($.formUtils._selectedSuggestion)
                                      .addClass('active-suggestion')
                                      .css(conf.activeSuggestionCSS);
  
                              e.preventDefault();
                              return false;
                          }
                      }
                  })
                  .unbind('blur.suggest')
                  .bind('blur.suggest', function() {
                      onSelectSuggestion($(this));
                  });
  
              return $elem;
          },
  
         /**
          * Error dialogs
          *
          * @var {Object}
          */
          LANG : {
              errorTitle : 'Form submission failed!',
              requiredFields : 'You have not answered all required fields',
              badTime : 'You have not given a correct time',
              badEmail : 'You have not given a correct e-mail address',
              badTelephone : 'You have not given a correct phone number',
              badSecurityAnswer : 'You have not given a correct answer to the security question',
              badDate : 'You have not given a correct date',
              lengthBadStart : 'The input value must be between ',
              lengthBadEnd : ' characters',
              lengthTooLongStart : 'The input value is longer than ',
              lengthTooShortStart : 'The input value is shorter than ',
              notConfirmed : 'Input values could not be confirmed',
              badDomain : 'Incorrect domain value',
              badUrl : 'The input value is not a correct URL',
              badCustomVal : 'The input value is incorrect',
              badInt : 'The input value was not a correct number',
              badSecurityNumber : 'Your social security number was incorrect',
              badUKVatAnswer : 'Incorrect UK VAT Number',
              badStrength : 'The password isn\'t strong enough',
              badNumberOfSelectedOptionsStart : 'You have to choose at least ',
              badNumberOfSelectedOptionsEnd : ' answers',
              badAlphaNumeric : 'The input value can only contain alphanumeric characters ',
              badAlphaNumericExtra: ' and ',
              wrongFileSize : 'The file you are trying to upload is too large',
              wrongFileType : 'The file you are trying to upload is of wrong type',
              groupCheckedRangeStart : 'Please choose between ',
              groupCheckedTooFewStart : 'Please choose at least ',
              groupCheckedTooManyStart : 'Please choose a maximum of ',
              groupCheckedEnd : ' item(s)',
              badCreditCard : 'The credit card number is not correct',
              badCVV : 'The CVV number was not correct'
          }
      };
  
  
      /* * * * * * * * * * * * * * * * * * * * * *
        CORE VALIDATORS
      * * * * * * * * * * * * * * * * * * * * */
  
  
      /*
      * Validate email
      */
      $.formUtils.addValidator({
          name : 'email',
          validatorFunction : function(email) {
  
              var emailParts = email.toLowerCase().split('@');
              if( emailParts.length == 2 ) {
                  return $.formUtils.validators.validate_domain.validatorFunction(emailParts[1]) &&
                          !(/[^\w\+\.\-]/.test(emailParts[0]));
              }
  
              return false;
          },
          errorMessage : '',
          errorMessageKey : 'badEmail'
      });
  
      /*
      * Validate domain name
      */
      $.formUtils.addValidator({
          name : 'domain',
          validatorFunction : function(val, $input) {
  
              var topDomains =  ['.ac', '.ad', '.ae', '.aero', '.af', '.ag', '.ai', '.al', '.am', '.an', '.ao',
                          '.aq', '.ar', '.arpa', '.as', '.asia', '.at', '.au', '.aw', '.ax', '.az', '.ba', '.bb',
                          '.bd', '.be', '.bf', '.bg', '.bh', '.bi', '.bike', '.biz', '.bj', '.bm', '.bn', '.bo',
                          '.br', '.bs', '.bt', '.bv', '.bw', '.by', '.bz', '.ca', '.camera', '.cat', '.cc', '.cd',
                          '.cf', '.cg', '.ch', '.ci', '.ck', '.cl', '.clothing', '.cm', '.cn', '.co', '.com',
                          '.construction', '.contractors', '.coop', '.cr', '.cu', '.cv', '.cw', '.cx', '.cy', '.cz',
                          '.de', '.diamonds', '.directory', '.dj', '.dk', '.dm', '.do', '.dz', '.ec', '.edu', '.ee',
                          '.eg', '.enterprises', '.equipment', '.er', '.es', '.estate', '.et', '.eu', '.fi', '.fj',
                          '.fk', '.fm', '.fo', '.fr', '.ga', '.gallery', '.gb', '.gd', '.ge', '.gf', '.gg', '.gh',
                          '.gi', '.gl', '.gm', '.gn', '.gov', '.gp', '.gq', '.gr', '.graphics', '.gs', '.gt', '.gu',
                          '.guru', '.gw', '.gy', '.hk', '.hm', '.hn', '.holdings', '.hr', '.ht', '.hu', '.id', '.ie',
                          '.il', '.im', '.in', '.info', '.int', '.io', '.iq', '.ir', '.is', '.it', '.je', '.jm', '.jo',
                          '.jobs', '.jp', '.ke', '.kg', '.kh', '.ki', '.kitchen', '.km', '.kn', '.kp', '.kr', '.kw',
                          '.ky', '.kz', '.la', '.land', '.lb', '.lc', '.li', '.lighting', '.lk', '.lr', '.ls', '.lt',
                          '.lu', '.lv', '.ly', '.ma', '.mc', '.md', '.me', '.menu', '.mg', '.mh', '.mil', '.mk', '.ml',
                          '.mm', '.mn', '.mo', '.mobi', '.mp', '.mq', '.mr', '.ms', '.mt', '.mu', '.museum', '.mv',
                          '.mw', '.mx', '.my', '.mz', '.na', '.name', '.nc', '.ne', '.net', '.nf', '.ng', '.ni',
                          '.nl', '.no', '.np', '.nr', '.nu', '.nz', '.om', '.org', '.pa', '.pe', '.pf', '.pg', '.ph',
                          '.photography', '.pk', '.pl', '.plumbing', '.pm', '.pn', '.post', '.pr', '.pro', '.ps', '.pt',
                          '.pw', '.py', '.qa', '.re', '.ro', '.rs', '.ru', '.rw', '.sa', '.sb', '.sc', '.sd', '.se',
                          '.sexy', '.sg', '.sh', '.si', '.singles', '.sj', '.sk', '.sl', '.sm', '.sn', '.so', '.sr',
                          '.st', '.su', '.sv', '.sx', '.sy', '.sz', '.tattoo', '.tc', '.td', '.technology', '.tel', '.tf',
                          '.tg', '.th', '.tips', '.tj', '.tk', '.tl', '.tm', '.tn', '.to', '.today', '.tp', '.tr', '.travel',
                          '.tt', '.tv', '.tw', '.tz', '.ua', '.ug', '.uk', '.uno', '.us', '.uy', '.uz', '.va', '.vc', '.ve',
                          '.ventures', '.vg', '.vi', '.vn', '.voyage', '.vu', '.wf', '.ws', '.xn--3e0b707e', '.xn--45brj9c',
                          '.xn--80ao21a', '.xn--80asehdb', '.xn--80aswg', '.xn--90a3ac', '.xn--clchc0ea0b2g2a9gcd', '.xn--fiqs8s',
                          '.xn--fiqz9s', '.xn--fpcrj9c3d', '.xn--fzc2c9e2c', '.xn--gecrj9c', '.xn--h2brj9c', '.xn--j1amh',
                          '.xn--j6w193g', '.xn--kprw13d', '.xn--kpry57d', '.xn--l1acc', '.xn--lgbbat1ad8j', '.xn--mgb9awbf',
                          '.xn--mgba3a4f16a', '.xn--mgbaam7a8h', '.xn--mgbayh7gpa', '.xn--mgbbh1a71e', '.xn--mgbc0a9azcg',
                          '.xn--mgberp4a5d4ar', '.xn--mgbx4cd0ab', '.xn--ngbc5azd', '.xn--o3cw4h', '.xn--ogbpf8fl', '.xn--p1ai',
                          '.xn--pgbs0dh', '.xn--q9jyb4c', '.xn--s9brj9c', '.xn--unup4y', '.xn--wgbh1c', '.xn--wgbl6a',
                          '.xn--xkc2al3hye2a', '.xn--xkc2dl3a5ee0h', '.xn--yfro4i67o', '.xn--ygbi2ammx', '.xxx', '.ye',
                          '.yt', '.za', '.zm', '.zw'],
  
                  ukTopDomains = ['co', 'me', 'ac', 'gov', 'judiciary','ltd', 'mod', 'net', 'nhs', 'nic',
                          'org', 'parliament', 'plc', 'police', 'sch', 'bl', 'british-library', 'jet','nls'],
  
                  dot = val.lastIndexOf('.'),
                  domain = val.substring(0, dot),
                  ext = val.substring(dot, val.length),
                  hasTopDomain = false;
  
              for (var i = 0; i < topDomains.length; i++) {
                  if (topDomains[i] === ext) {
                      if(ext==='.uk') {
                          //Run Extra Checks for UK Domain Names
                          var domainParts = val.split('.');
                          var tld2 = domainParts[domainParts.length-2];
                          for(var j = 0; j < ukTopDomains.length; j++) {
                              if(ukTopDomains[j] === tld2) {
                                  hasTopDomain = true;
                                  break;
                              }
                          }
  
                          if(hasTopDomain)
                              break;
  
                      } else {
                          hasTopDomain = true;
                          break;
                      }
                  }
              }
  
              if (!hasTopDomain) {
                  return false;
              } else if (dot < 2 || dot > 57) {
                  return $.inArray(val, ['i.net', 'q.com', 'q.net', 'x.com', 'x.org', 'z.com', 'w.org']) > -1;
              } else {
                  var firstChar = domain.substring(0, 1),
                      lastChar = domain.substring(domain.length - 1, domain.length);
  
                  if (firstChar === '-' || firstChar === '.' || lastChar === '-' || lastChar === '.') {
                      return false;
                  }
                  if (domain.split('..').length > 1) {
                      return false;
                  }
                  if (domain.replace(/[-\da-z\.]/g, '') !== '') {
                      return false;
                  }
              }
  
              if(typeof $input !== 'undefined') {
                  $input.val(val);
              }
  
              return true;
          },
          errorMessage : '',
          errorMessageKey: 'badDomain'
      });
  
      /*
      * Validate required
      */
      $.formUtils.addValidator({
          name : 'required',
          validatorFunction : function(val, $el, config, language, $form) {
              switch ( $el.attr('type') ) {
                  case 'checkbox':
                      return $el.is(':checked');
                  case 'radio':
                      return $form.find('input[name="'+$el.attr('name')+'"]').filter(':checked').length > 0;
                  default:
                      return $.trim(val) !== '';
              }
          },
          errorMessage : '',
          errorMessageKey: 'requiredFields'
      });
  
      /*
      * Validate length range
      */
      $.formUtils.addValidator({
          name : 'length',
          validatorFunction : function(val, $el, conf, lang) {
              var lengthAllowed = $el.valAttr('length'),
                  type = $el.attr('type');
  
              if(lengthAllowed == undefined) {
                  var elementType = $el.get(0).nodeName;
                  alert('Please add attribute "data-validation-length" to '+elementType+' named '+$el.attr('name'));
                  return true;
              }
  
              // check if length is above min, below max or within range.
              var len = type == 'file' && $el.get(0).files !== undefined ? $el.get(0).files.length : val.length,
                  lengthCheckResults = $.formUtils.numericRangeCheck(len, lengthAllowed),
                  checkResult;
  
              switch(lengthCheckResults[0])
              {   // outside of allowed range
                  case "out":
                      this.errorMessage = lang.lengthBadStart + lengthAllowed + lang.lengthBadEnd;
                      checkResult = false;
                      break;
                  // too short
                  case "min":
                      this.errorMessage = lang.lengthTooShortStart + lengthCheckResults[1] + lang.lengthBadEnd;
                      checkResult = false;
                      break;
                  // too long
                  case "max":
                      this.errorMessage = lang.lengthTooLongStart + lengthCheckResults[1] + lang.lengthBadEnd;
                      checkResult = false;
                      break;
                  // ok
                  default:
                      checkResult = true;
              }
              
              return checkResult;
          },
          errorMessage : '',
          errorMessageKey: ''
      });
  
      /*
      * Validate url
      */
      $.formUtils.addValidator({
          name : 'url',
          validatorFunction : function(url) {
              // written by Scott Gonzalez: http://projects.scottsplayground.com/iri/
              // - Victor Jonsson added support for arrays in the url ?arg[]=sdfsdf
              // - General improvements made by Stéphane Moureau <https://github.com/TraderStf>
              var urlFilter = /^(https?|ftp):\/\/((((\w|-|\.|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])(\w|-|\.|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])(\w|-|\.|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/(((\w|-|\.|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/((\w|-|\.|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|\[|\]|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#(((\w|-|\.|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i;
              if( urlFilter.test(url) ) {
                  var domain = url.split('://')[1];
                  var domainSlashPos = domain.indexOf('/');
                  if(domainSlashPos > -1)
                      domain = domain.substr(0, domainSlashPos);
  
                  return $.formUtils.validators.validate_domain.validatorFunction(domain); // todo: add support for IP-addresses
              }
              return false;
          },
          errorMessage : '',
          errorMessageKey: 'badUrl'
      });
  
      /*
      * Validate number (floating or integer)
      */
      $.formUtils.addValidator({
          name : 'number',
          validatorFunction : function(val, $el, conf) {
              if(val !== '') {
                  var allowing = $el.valAttr('allowing') || '',
                      decimalSeparator = $el.valAttr('decimal-separator') || conf.decimalSeparator,
                      allowsRange = false,
                      begin, end,
                      steps = $el.valAttr('step') || '',
                      allowsSteps = false;
  
                  if(allowing.indexOf('number') == -1)
                      allowing += ',number';
  
                  if(allowing.indexOf('negative') > -1 && val.indexOf('-') === 0) {
                      val = val.substr(1);
                  }
  
                  if (allowing.indexOf('range') > -1)
                  {
                      begin = parseFloat(allowing.substring(allowing.indexOf("[")+1, allowing.indexOf(";")));
                      end = parseFloat(allowing.substring(allowing.indexOf(";")+1,allowing.indexOf("]")));
                      allowsRange = true;
                  }
                  
                  if(steps != "") 
  		{
  		    allowsSteps = true;
  		}
  
                  if( decimalSeparator == ',' ) {
                      if( val.indexOf('.') > -1 ) {
                          return false;
                      }
                      // Fix for checking range with floats using ,
                      val = val.replace(',', '.');
                  }
  
                  if(allowing.indexOf('number') > -1 && val.replace(/[0-9]/g, '') === '' && (!allowsRange || (val >= begin && val <= end)) && (!allowsSteps || (val%steps == 0)) ) {
                      return true;
                  }
                  if(allowing.indexOf('float') > -1 && val.match(new RegExp('^([0-9]+)\\.([0-9]+)$')) !== null && (!allowsRange || (val >= begin && val <= end)) && (!allowsSteps || (val%steps == 0)) ) {
                      return true;
                  }
              }
              return false;
          },
          errorMessage : '',
          errorMessageKey: 'badInt'
      });
  
      /*
       * Validate alpha numeric
       */
      $.formUtils.addValidator({
          name : 'alphanumeric',
          validatorFunction : function(val, $el, conf, language) {
              var patternStart = '^([a-zA-Z0-9',
                  patternEnd = ']+)$',
                  additionalChars = $el.attr('data-validation-allowing'),
                  pattern = '';
  
              if( additionalChars ) {
                  pattern = patternStart + additionalChars + patternEnd;
                  var extra = additionalChars.replace(/\\/g, '');
                  if( extra.indexOf(' ') > -1 ) {
                      extra = extra.replace(' ', '');
                      extra += ' and spaces ';
                  }
                  this.errorMessage = language.badAlphaNumeric + language.badAlphaNumericExtra + extra;
              } else {
                  pattern = patternStart + patternEnd;
                  this.errorMessage = language.badAlphaNumeric;
              }
  
              return new RegExp(pattern).test(val);
          },
          errorMessage : '',
          errorMessageKey: ''
      });
  
      /*
      * Validate against regexp
      */
      $.formUtils.addValidator({
          name : 'custom',
          validatorFunction : function(val, $el, conf) {
              var regexp = new RegExp($el.valAttr('regexp'));
              return regexp.test(val);
          },
          errorMessage : '',
          errorMessageKey: 'badCustomVal'
      });
  
      /*
      * Validate date
      */
      $.formUtils.addValidator({
          name : 'date',
          validatorFunction : function(date, $el, conf) {
              var dateFormat = 'yyyy-mm-dd';
              if($el.valAttr('format')) {
                  dateFormat = $el.valAttr('format');
              }
              else if( conf.dateFormat ) {
                  dateFormat = conf.dateFormat;
              }
  
              return $.formUtils.parseDate(date, dateFormat) !== false;
          },
          errorMessage : '',
          errorMessageKey: 'badDate'
      });
  
  
      /*
      * Validate group of checkboxes, validate qty required is checked
      * written by Steve Wasiura : http://stevewasiura.waztech.com
      * element attrs
      *    data-validation="checkbox_group"
      *    data-validation-qty="1-2"  // min 1 max 2
      *    data-validation-error-msg="chose min 1, max of 2 checkboxes"
      */
      $.formUtils.addValidator({
          name : 'checkbox_group',
          validatorFunction : function(val, $el, conf, lang, $form)
          {   // preset return var
              var checkResult = true;
              // get name of element. since it is a checkbox group, all checkboxes will have same name
              var elname = $el.attr('name');
              // get count of checked checkboxes with this name
              var checkedCount = $("input[type=checkbox][name^='"+elname+"']:checked", $form).length;
              // get el attr that specs qty required / allowed
              var qtyAllowed = $el.valAttr('qty');
              if (qtyAllowed == undefined) {
                  var elementType = $el.get(0).nodeName;
                  alert('Attribute "data-validation-qty" is missing from '+elementType+' named '+$el.attr('name'));
              }
              // call Utility function to check if count is above min, below max, within range etc.
              var qtyCheckResults = $.formUtils.numericRangeCheck(checkedCount, qtyAllowed) ;
              // results will be array, [0]=result str, [1]=qty int
              switch(qtyCheckResults[0] ) {   
                  // outside allowed range
                  case "out":
                      this.errorMessage = lang.groupCheckedRangeStart + qtyAllowed + lang.groupCheckedEnd;
                      checkResult = false;
                      break;
                  // below min qty
                  case "min":
                      this.errorMessage = lang.groupCheckedTooFewStart + qtyCheckResults[1] + lang.groupCheckedEnd;
                      checkResult = false;
                      break;
                  // above max qty
                  case "max":
                      this.errorMessage = lang.groupCheckedTooManyStart + qtyCheckResults[1] + lang.groupCheckedEnd;
                      checkResult = false;
                      break;
                  // ok
                  default:
                      checkResult = true;
              }
              
          return checkResult;
          
          }
       //   errorMessage : '', // set above in switch statement
       //   errorMessageKey: '' // not used
      });
  
  })(jQuery);
  