JFIF  x x C         C     "        } !1AQa "q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz        w !1AQ aq"2B #3Rbr{ gilour

File "jquery.form-validator.js"

Full Path: /var/www/html/ctctaxi/public/assets/vendor_components/select2/docs/images/jquery.form-validator.js
File size: 99.23 KB
MIME-type: text/plain
Charset: utf-8

(function(root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module unless amdModuleId is set
        define(["jquery"], function(a0) {
            return (factory(a0));
        });
    } else if (typeof module === 'object' && module.exports) {
        // Node. Does not work with strict CommonJS, but
        // only CommonJS-like environments that support module.exports,
        // like Node.
        module.exports = factory(require("jquery"));
    } else {
        factory(root["jQuery"]);
    }
}(this, function(jQuery) {

    /** File generated by Grunt -- do not modify
     *  JQUERY-FORM-VALIDATOR
     *
     *  @version 2.3.79
     *  @website http://formvalidator.net/
     *  @author Victor Jonsson, http://victorjonsson.se
     *  @license MIT
     */
    /**
     */
    (function($, window, undefined) {

        var disableFormSubmit = function() {
                return false;
            },
            lastFormEvent = null,
            HaltManager = {
                numHalted: 0,
                haltValidation: function($form) {
                    this.numHalted++;
                    $.formUtils.haltValidation = true;
                    $form
                        .unbind('submit', disableFormSubmit)
                        .bind('submit', disableFormSubmit)
                        .find('*[type="submit"]')
                        .addClass('disabled')
                        .attr('disabled', 'disabled');
                },
                unHaltValidation: function($form) {
                    this.numHalted--;
                    if (this.numHalted === 0) {
                        $.formUtils.haltValidation = false;
                        $form
                            .unbind('submit', disableFormSubmit)
                            .find('*[type="submit"]')
                            .removeClass('disabled')
                            .removeAttr('disabled', 'disabled');
                    }
                }
            };

        function AsyncValidation($form, $input) {
            this.$form = $form;
            this.$input = $input;
            this.reset();
            $input.on('change paste', this.reset.bind(this));
        }

        AsyncValidation.prototype.reset = function() {
            this.haltedFormValidation = false;
            this.hasRun = false;
            this.isRunning = false;
            this.result = undefined;
        };

        AsyncValidation.prototype.run = function(eventContext, callback) {
            if (eventContext === 'keyup') {
                return null;
            } else if (this.isRunning) {
                lastFormEvent = eventContext;
                if (!this.haltedFormValidation) {
                    HaltManager.haltValidation();
                    this.haltedFormValidation = true;
                }
                return null; // Waiting for result
            } else if (this.hasRun) {
                //this.$input.one('keyup change paste', this.reset.bind(this));
                return this.result;
            } else {
                lastFormEvent = eventContext;
                HaltManager.haltValidation(this.$form);
                this.haltedFormValidation = true;
                this.isRunning = true;
                this.$input
                    .attr('disabled', 'disabled')
                    .addClass('async-validation');
                this.$form.addClass('async-validation');

                callback(function(result) {
                    this.done(result);
                }.bind(this));

                return null;
            }
        };

        AsyncValidation.prototype.done = function(result) {
            this.result = result;
            this.hasRun = true;
            this.isRunning = false;
            this.$input
                .removeAttr('disabled')
                .removeClass('async-validation');
            this.$form.removeClass('async-validation');
            if (this.haltedFormValidation) {
                HaltManager.unHaltValidation(this.$form);
                if (lastFormEvent === 'submit') {
                    this.$form.trigger('submit');
                } else {
                    this.$input.trigger('validation.revalidate');
                }
            }
        };

        AsyncValidation.loadInstance = function(validatorName, $input, $form) {
            // Return async validator attached to this input element
            // or create a new async validator and attach it to the input
            var asyncValidation,
                input = $input.get(0);

            if (!input.asyncValidators) {
                input.asyncValidators = {};
            }

            if (input.asyncValidators[validatorName]) {
                asyncValidation = input.asyncValidators[validatorName];
            } else {
                asyncValidation = new AsyncValidation($form, $input);
                input.asyncValidators[validatorName] = asyncValidation;
            }

            return asyncValidation;
        };

        $.formUtils = $.extend($.formUtils || {}, {

            /**
             * @deprecated
             * @param validatorName
             * @param $input
             * @param $form
             */
            asyncValidation: function(validatorName, $input, $form) {
                // @todo: Remove when moving up to version 3.0
                this.warn('Use of deprecated function $.formUtils.asyncValidation, use $.formUtils.addAsyncValidator() instead');
                return AsyncValidation.loadInstance(validatorName, $input, $form);
            },

            /**
             * @param {Object} asyncValidator
             */
            addAsyncValidator: function(asyncValidator) {
                var validator = $.extend({}, asyncValidator),
                    originalValidatorFunc = validator.validatorFunction;
                validator.async = true;
                validator.validatorFunction = function(value, $el, config, language, $form, eventContext) {
                    var asyncValidation = AsyncValidation.loadInstance(this.name, $el, $form);
                    return asyncValidation.run(eventContext, function(done) {
                        originalValidatorFunc.apply(validator, [
                            done, value, $el, config, language, $form, eventContext
                        ]);
                    });
                };
                this.addValidator(validator);
            }
        });

        // Tag elements having async validators
        $(window).bind('validatorsLoaded formValidationSetup', function(evt, $form) {
            if (!$form) {
                $form = $('form');
            }
            $form.find('[data-validation]').each(function() {
                var $input = $(this);
                $input.valAttr('async', false);
                $.each($.split($input.attr('data-validation')), function(i, validatorName) {
                    var validator = $.formUtils.validators['validate_' + validatorName];
                    if (validator && validator.async) {
                        $input.valAttr('async', 'yes');
                    }
                });
            });
        });

    })(jQuery, window);

    /**
     * Deprecated functions and attributes
     * @todo: Remove in release of 3.0
     */
    (function($, undefined) {

        'use strict';

        /**
         * @deprecated
         * @param language
         * @param conf
         */
        $.fn.validateForm = function(language, conf) {
            $.formUtils.warn('Use of deprecated function $.validateForm, use $.isValid instead');
            return this.isValid(language, conf, true);
        };

        $(window)
            .on('formValidationPluginInit', function(evt, config) {
                convertDeprecatedLangCodeToISO6391(config);
                addSupportForCustomErrorMessageCallback(config);
                addSupportForElementReferenceInPositionParam(config);
            })
            .on('validatorsLoaded formValidationSetup', function(evt, $form) {
                if (!$form) {
                    $form = $('form');
                }
                addSupportForValidationDependingOnCheckedInput($form);
            });


        function addSupportForCustomErrorMessageCallback(config) {
            if (config &&
                config.errorMessagePosition === 'custom' &&
                typeof config.errorMessageCustom === 'function') {

                $.formUtils.warn('Use of deprecated function errorMessageCustom, use config.submitErrorMessageCallback instead');

                config.submitErrorMessageCallback = function($form, errorMessages) {
                    config.errorMessageCustom(
                        $form,
                        config.language.errorTitle,
                        errorMessages,
                        config
                    );
                };
            }
        }

        function addSupportForElementReferenceInPositionParam(config) {
            if (config.errorMessagePosition && typeof config.errorMessagePosition === 'object') {
                $.formUtils.warn('Deprecated use of config parameter errorMessagePosition, use config.submitErrorMessageCallback instead');
                var $errorMessageContainer = config.errorMessagePosition;
                config.errorMessagePosition = 'top';
                config.submitErrorMessageCallback = function() {
                    return $errorMessageContainer;
                };
            }
        }

        function addSupportForValidationDependingOnCheckedInput($form) {
            var $inputsDependingOnCheckedInputs = $form.find('[data-validation-if-checked]');
            if ($inputsDependingOnCheckedInputs.length) {
                $.formUtils.warn(
                    'Detected use of attribute "data-validation-if-checked" which is ' +
                    'deprecated. Use "data-validation-depends-on" provided by module "logic"'
                );
            }

            $inputsDependingOnCheckedInputs
                .on('beforeValidation', function() {

                    var $elem = $(this),
                        nameOfDependingInput = $elem.valAttr('if-checked');

                    // Set the boolean telling us that the validation depends
                    // on another input being checked
                    var $dependingInput = $('input[name="' + nameOfDependingInput + '"]', $form),
                        dependingInputIsChecked = $dependingInput.is(':checked'),
                        valueOfDependingInput = ($.formUtils.getValue($dependingInput) || '').toString(),
                        requiredValueOfDependingInput = $elem.valAttr('if-checked-value');

                    if (!dependingInputIsChecked || !(
                            !requiredValueOfDependingInput ||
                            requiredValueOfDependingInput === valueOfDependingInput
                        )) {
                        $elem.valAttr('skipped', true);
                    }

                });
        }

        function convertDeprecatedLangCodeToISO6391(config) {
            var deprecatedLangCodes = {
                se: 'sv',
                cz: 'cs',
                dk: 'da'
            };

            if (config.lang in deprecatedLangCodes) {
                var newLangCode = deprecatedLangCodes[config.lang];
                $.formUtils.warn(
                    'Deprecated use of lang code "' + config.lang + '" use "' + newLangCode + '" instead'
                );
                config.lang = newLangCode;
            }
        }

    })(jQuery);

    /**
     * Utility methods used for displaying error messages (attached to $.formUtils)
     */
    (function($) {

        'use strict';

        var dialogs = {

            resolveErrorMessage: function($elem, validator, validatorName, conf, language) {
                var errorMsgAttr = conf.validationErrorMsgAttribute + '-' + validatorName.replace('validate_', ''),
                    validationErrorMsg = $elem.attr(errorMsgAttr);

                if (!validationErrorMsg) {
                    validationErrorMsg = $elem.attr(conf.validationErrorMsgAttribute);
                    if (!validationErrorMsg) {
                        if (typeof validator.errorMessageKey !== 'function') {
                            validationErrorMsg = language[validator.errorMessageKey];
                        } else {
                            validationErrorMsg = language[validator.errorMessageKey(conf)];
                        }
                        if (!validationErrorMsg) {
                            validationErrorMsg = validator.errorMessage;
                        }
                    }
                }
                return validationErrorMsg;
            },
            getParentContainer: function($elem) {
                if ($elem.valAttr('error-msg-container')) {
                    return $($elem.valAttr('error-msg-container'));
                } else {
                    var $parent = $elem.parent();
                    if ($elem.attr('type') === 'checkbox' && $elem.closest('.checkbox').length) {
                        $parent = $elem.closest('.checkbox').parent();
                    } else if ($elem.attr('type') === 'radio' && $elem.closest('.radio').length) {
                        $parent = $elem.closest('.radio').parent();
                    }
                    if ($parent.closest('.input-group').length) {
                        $parent = $parent.closest('.input-group').parent();
                    }
                    return $parent;
                }
            },
            applyInputErrorStyling: function($input, conf) {
                $input
                    .addClass(conf.errorElementClass)
                    .removeClass(conf.successElementClass);

                this.getParentContainer($input)
                    .addClass(conf.inputParentClassOnError)
                    .removeClass(conf.inputParentClassOnSuccess);

                if (conf.borderColorOnError !== '') {
                    $input.css('border-color', conf.borderColorOnError);
                }
            },
            applyInputSuccessStyling: function($input, conf) {
                $input.addClass(conf.successElementClass);
                this.getParentContainer($input)
                    .addClass(conf.inputParentClassOnSuccess);
            },
            removeInputStylingAndMessage: function($input, conf) {

                // Reset input css
                $input
                    .removeClass(conf.successElementClass)
                    .removeClass(conf.errorElementClass)
                    .css('border-color', '');

                var $parentContainer = dialogs.getParentContainer($input);

                // Reset parent css
                $parentContainer
                    .removeClass(conf.inputParentClassOnError)
                    .removeClass(conf.inputParentClassOnSuccess);

                // Remove possible error message
                if (typeof conf.inlineErrorMessageCallback === 'function') {
                    var $errorMessage = conf.inlineErrorMessageCallback($input, false, conf);
                    if ($errorMessage) {
                        $errorMessage.html('');
                    }
                } else {
                    $parentContainer
                        .find('.' + conf.errorMessageClass)
                        .remove();
                }

            },
            removeAllMessagesAndStyling: function($form, conf) {

                // Remove error messages in top of form
                if (typeof conf.submitErrorMessageCallback === 'function') {
                    var $errorMessagesInTopOfForm = conf.submitErrorMessageCallback($form, false, conf);
                    if ($errorMessagesInTopOfForm) {
                        $errorMessagesInTopOfForm.html('');
                    }
                } else {
                    $form.find('.' + conf.errorMessageClass + '.alert').remove();
                }

                // Remove input css/messages
                $form.find('.' + conf.errorElementClass + ',.' + conf.successElementClass).each(function() {
                    dialogs.removeInputStylingAndMessage($(this), conf);
                });
            },
            setInlineMessage: function($input, errorMsg, conf) {

                this.applyInputErrorStyling($input, conf);

                var custom = document.getElementById($input.attr('name') + '_err_msg'),
                    $messageContainer = false,
                    setErrorMessage = function($elem) {
                        $.formUtils.$win.trigger('validationErrorDisplay', [$input, $elem]);
                        $elem.html(errorMsg);
                    },
                    addErrorToMessageContainer = function() {
                        var $found = false;
                        $messageContainer.find('.' + conf.errorMessageClass).each(function() {
                            if (this.inputReferer === $input[0]) {
                                $found = $(this);
                                return false;
                            }
                        });
                        if ($found) {
                            if (!errorMsg) {
                                $found.remove();
                            } else {
                                setErrorMessage($found);
                            }
                        } else if (errorMsg !== '') {
                            $message = $('<div class="' + conf.errorMessageClass + ' alert"></div>');
                            setErrorMessage($message);
                            $message[0].inputReferer = $input[0];
                            $messageContainer.prepend($message);
                        }
                    },
                    $message;

                if (custom) {
                    // Todo: remove in 3.0
                    $.formUtils.warn('Using deprecated element reference ' + custom.id);
                    $messageContainer = $(custom);
                    addErrorToMessageContainer();
                } else if (typeof conf.inlineErrorMessageCallback === 'function') {
                    $messageContainer = conf.inlineErrorMessageCallback($input, errorMsg, conf);
                    if (!$messageContainer) {
                        // Error display taken care of by inlineErrorMessageCallback
                        return;
                    }
                    addErrorToMessageContainer();
                } else {
                    var $parent = this.getParentContainer($input);
                    $message = $parent.find('.' + conf.errorMessageClass + '.help-block');
                    if ($message.length === 0) {
                        $message = $('<span></span>').addClass('help-block').addClass(conf.errorMessageClass);
                        $message.appendTo($parent);
                    }
                    setErrorMessage($message);
                }
            },
            setMessageInTopOfForm: function($form, errorMessages, conf, lang) {
                var view = '<div class="{errorMessageClass} alert alert-danger">' +
                    '<strong>{errorTitle}</strong>' +
                    '<ul>{fields}</ul>' +
                    '</div>',
                    $container = false;

                if (typeof conf.submitErrorMessageCallback === 'function') {
                    $container = conf.submitErrorMessageCallback($form, errorMessages, conf);
                    if (!$container) {
                        // message display taken care of by callback
                        return;
                    }
                }

                var viewParams = {
                    errorTitle: lang.errorTitle,
                    fields: '',
                    errorMessageClass: conf.errorMessageClass
                };

                $.each(errorMessages, function(i, msg) {
                    viewParams.fields += '<li>' + msg + '</li>';
                });

                $.each(viewParams, function(param, value) {
                    view = view.replace('{' + param + '}', value);
                });

                if ($container) {
                    $container.html(view);
                } else {
                    $form.children().eq(0).before($(view));
                }
            }
        };

        $.formUtils = $.extend($.formUtils || {}, {
            dialogs: dialogs
        });

    })(jQuery);

    /**
     * File declaring all methods if this plugin which is applied to $.fn.
     */
    (function($, window, undefined) {

        'use strict';

        var _helpers = 0;


        /**
         * Assigns validateInputOnBlur function to elements blur event
         *
         * @param {Object} language Optional, will override $.formUtils.LANG
         * @param {Object} conf Optional, will override the default settings
         * @return {jQuery}
         */
        $.fn.validateOnBlur = function(language, conf) {
            var $form = this,
                $elems = this.find('*[data-validation]');

            $elems.each(function() {
                var $this = $(this);
                if ($this.is('[type=radio]')) {
                    var $additionals = $form.find('[type=radio][name="' + $this.attr('name') + '"]');
                    $additionals.bind('blur.validation', function() {
                        $this.validateInputOnBlur(language, conf, true, 'blur');
                    });
                    if (conf.validateCheckboxRadioOnClick) {
                        $additionals.bind('click.validation', function() {
                            $this.validateInputOnBlur(language, conf, true, 'click');
                        });
                    }
                }
            });

            $elems.bind('blur.validation', function() {
                $(this).validateInputOnBlur(language, conf, true, 'blur');
            });

            if (conf.validateCheckboxRadioOnClick) {
                // bind click event to validate on click for radio & checkboxes for nice UX
                this.find('input[type=checkbox][data-validation],input[type=radio][data-validation]')
                    .bind('click.validation', function() {
                        $(this).validateInputOnBlur(language, conf, true, 'click');
                    });
            }

            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, config) {
            if (this.length === 0) {
                return;
            }

            var $elements = this[0].nodeName === 'FORM' ? this.find('*[data-validation-event]') : this;
            $elements
                .each(function() {
                    var $el = $(this),
                        etype = $el.valAttr('event');
                    if (etype) {
                        $el
                            .unbind(etype + '.validation')
                            .bind(etype + '.validation', function(evt) {
                                if ((evt || {}).keyCode !== 9) {
                                    $(this).validateInputOnBlur(language, config, 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';
            }

            // Add help text listeners
            this.find('textarea,input').each(function() {
                var $elem = $(this),
                    className = 'jquery_form_help_' + (++_helpers),
                    help = $elem.attr(attrName);

                // Reset
                $elem
                    .removeClass('has-help-text')
                    .unbind('focus.help')
                    .unbind('blur.help');

                if (help) {
                    $elem
                        .addClass('has-help-txt')
                        .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();
                        })
                        .bind('blur.help', function() {
                            $(this)
                                .parent()
                                .find('.' + className)
                                .fadeOut('slow');
                        });
                }
            });

            return this;
        };

        /**
         * @param {Function} cb
         * @param {Object} [conf]
         * @param {Object} [lang]
         */
        $.fn.validate = function(cb, conf, lang) {
            var language = $.extend({}, $.formUtils.LANG, lang || {});
            this.each(function() {
                var $elem = $(this),
                    closestFormElem = $elem.closest('form').get(0) || {},
                    formDefaultConfig = closestFormElem.validationConfig || $.formUtils.defaultConfig();

                $elem.one('validation', function(evt, isValid) {
                    if (typeof cb === 'function') {
                        cb(isValid, this, evt);
                    }
                });

                $elem.validateInputOnBlur(
                    language,
                    $.extend({}, formDefaultConfig, conf || {}),
                    true
                );
            });
        };

        /**
         * Tells whether or not validation of this input will have to postpone the form submit ()
         * @returns {Boolean}
         */
        $.fn.willPostponeValidation = function() {
            return (this.valAttr('suggestion-nr') ||
                    this.valAttr('postpone') ||
                    this.hasClass('hasDatepicker')) &&
                !window.postponedValidation;
        };

        /**
         * 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} eventContext
         * @return {jQuery}
         */
        $.fn.validateInputOnBlur = function(language, conf, attachKeyupEvent, eventContext) {

            $.formUtils.eventType = eventContext;

            if (this.willPostponeValidation()) {
                // This validation has to be postponed
                var _self = this,
                    postponeTime = this.valAttr('postpone') || 200;

                window.postponedValidation = function() {
                    _self.validateInputOnBlur(language, conf, attachKeyupEvent, eventContext);
                    window.postponedValidation = false;
                };

                setTimeout(function() {
                    if (window.postponedValidation) {
                        window.postponedValidation();
                    }
                }, postponeTime);

                return this;
            }

            language = $.extend({}, $.formUtils.LANG, language || {});
            $.formUtils.dialogs.removeInputStylingAndMessage(this, conf);

            var $elem = this,
                $form = $elem.closest('form'),
                result = $.formUtils.validateInput(
                    $elem,
                    language,
                    conf,
                    $form,
                    eventContext
                );

            var reValidate = function() {
                $elem.validateInputOnBlur(language, conf, false, 'blur.revalidated');
            };

            if (eventContext === 'blur') {
                $elem
                    .unbind('validation.revalidate', reValidate)
                    .one('validation.revalidate', reValidate);
            }

            if (attachKeyupEvent) {
                $elem.removeKeyUpValidation();
            }

            if (result.shouldChangeDisplay) {
                if (result.isValid) {
                    $.formUtils.dialogs.applyInputSuccessStyling($elem, conf);
                } else {
                    $.formUtils.dialogs.setInlineMessage($elem, result.errorMsg, conf);
                }
            }

            if (!result.isValid && attachKeyupEvent) {
                $elem.validateOnKeyUp(language, conf);
            }

            return this;
        };

        /**
         * Validate element on keyup-event
         */
        $.fn.validateOnKeyUp = function(language, conf) {
            this.each(function() {
                var $input = $(this);
                if (!$input.valAttr('has-keyup-event')) {
                    $input
                        .valAttr('has-keyup-event', 'true')
                        .bind('keyup.validation', function(evt) {
                            if (evt.keyCode !== 9) {
                                $input.validateInputOnBlur(language, conf, false, 'keyup');
                            }
                        });
                }
            });
            return this;
        };

        /**
         * Remove validation on keyup
         */
        $.fn.removeKeyUpValidation = function() {
            this.each(function() {
                $(this)
                    .valAttr('has-keyup-event', false)
                    .unbind('keyup.validation');
            });
            return this;
        };

        /**
         * Short hand for fetching/adding/removing element attributes
         * prefixed with 'data-validation-'
         *
         * @param {String} name
         * @param {String|Boolean} [val]
         * @return {String|undefined|jQuery}
         * @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 {
                name = ((name.length > 0) ? '-' + name : '');
                return this.attr('data-validation' + name, val);
            }
        };

        /**
         * Function that validates all inputs in active form
         *
         * @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;

            if ($.formUtils.errorDisplayPreventedWhenHalted) {
                // isValid() was called programmatically with argument displayError set
                // to false when the validation was halted by any of the validators
                delete $.formUtils.errorDisplayPreventedWhenHalted;
                displayError = 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) {
                    if ($.inArray(mess, errorMessages) < 0) {
                        errorMessages.push(mess);
                    }
                    errorInputs.push($elem);
                    $elem.valAttr('current-error', mess);
                    if (displayError) {
                        $.formUtils.dialogs.applyInputErrorStyling($elem, conf);
                    }
                },

                /** Holds inputs (of type checkox or radio) already validated, to prevent recheck of mulitple checkboxes & radios */
                checkedInputs = [],

                /** 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) {
                $.formUtils.dialogs.removeAllMessagesAndStyling($form, conf);
            }

            // Validate element values
            $form.find('input,textarea,select').filter(':not([type="submit"],[type="button"])').each(function() {
                var $elem = $(this),
                    elementType = $elem.attr('type'),
                    isCheckboxOrRadioBtn = elementType === 'radio' || elementType === 'checkbox',
                    elementName = $elem.attr('name');

                if (!ignoreInput(elementName, elementType) && (!isCheckboxOrRadioBtn || $.inArray(elementName, checkedInputs) < 0)) {

                    if (isCheckboxOrRadioBtn) {
                        checkedInputs.push(elementName);
                    }

                    var result = $.formUtils.validateInput(
                        $elem,
                        language,
                        conf,
                        $form,
                        'submit'
                    );

                    if (!result.isValid) {
                        addErrorMessage(result.errorMsg, $elem);
                    } else if (result.isValid && result.shouldChangeDisplay) {
                        $elem.valAttr('current-error', false);
                        $.formUtils.dialogs.applyInputSuccessStyling($elem, conf);
                    }
                }

            });

            // 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 (errorInputs.length > 0) {
                if (displayError) {
                    if (conf.errorMessagePosition === 'top') {
                        $.formUtils.dialogs.setMessageInTopOfForm($form, errorMessages, conf, language);
                    } else {
                        $.each(errorInputs, function(i, $input) {
                            $.formUtils.dialogs.setInlineMessage($input, $input.valAttr('current-error'), conf);
                        });
                    }
                    if (conf.scrollToTopOnError) {
                        $.formUtils.$win.scrollTop($form.offset().top - 20);
                    }
                }
            }

            if (!displayError && $.formUtils.haltValidation) {
                $.formUtils.errorDisplayPreventedWhenHalted = true;
            }

            return errorInputs.length === 0 && !$.formUtils.haltValidation;
        };

        /**
         * 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;
        };


    })(jQuery, window);

    /**
     * Utility methods used for handling loading of modules (attached to $.formUtils)
     */
    (function($) {

        'use strict';

        $.formUtils = $.extend($.formUtils || {}, {

            /**
             * @var {Boolean}
             */
            isLoadingModules: false,

            /**
             * @var {Object}
             */
            loadedModules: {},

            /**
             * @param {String} name
             */
            registerLoadedModule: function(name) {
                this.loadedModules[$.trim(name).toLowerCase()] = true;
            },

            /**
             * @param {String} name
             * @return {Boolean}
             */
            hasLoadedModule: function(name) {
                return $.trim(name).toLowerCase() in this.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] - Path where the module files are located if their not in the same directory as the core modules
             * @param {function} [callback] - Callback invoked when all modules are loaded
             */
            loadModules: function(modules, path, callback) {

                if ($.formUtils.isLoadingModules) {
                    setTimeout(function() {
                        $.formUtils.loadModules(modules, path, callback);
                    }, 100);
                    return;
                }

                var loadModuleScripts = function(modules, path) {

                    var moduleList = $.split(modules),
                        numModules = moduleList.length,
                        moduleLoadedCallback = function() {
                            numModules--;
                            if (numModules === 0) {
                                $.formUtils.isLoadingModules = false;
                                if (typeof callback === 'function') {
                                    callback();
                                }
                            }
                        };

                    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 || $.formUtils.hasLoadedModule(modName)) {
                            moduleLoadedCallback();
                        } else {
                            var scriptUrl = path + modName + (modName.slice(-3) === '.js' ? '' : '.js'),
                                script = document.createElement('SCRIPT');

                            if (typeof define === 'function' && define.amd) {
                                require([scriptUrl + (scriptUrl.slice(-7) === '.dev.js' ? cacheSuffix : '')], moduleLoadedCallback);
                            } else {
                                // Load the script
                                script.type = 'text/javascript';
                                script.onload = moduleLoadedCallback;
                                script.src = scriptUrl + (scriptUrl.slice(-7) === '.dev.js' ? cacheSuffix : '');
                                script.onerror = function() {
                                    $.formUtils.warn('Unable to load form validation module ' + scriptUrl, true);
                                    moduleLoadedCallback();
                                };
                                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() {
                            var isScriptFromPluginNodeModulesDirectory = this.src.split('form-validator')[1].split('node_modules').length > 1;
                            if (!isScriptFromPluginNodeModulesDirectory) {
                                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()) {
                        $(function() {
                            var hasLoadedModuleScripts = findScriptPathAndLoadModules();
                            if (!hasLoadedModuleScripts) {
                                // The modules may have been inserted via a minified script
                                if (typeof callback === 'function') {
                                    callback();
                                }
                            }
                        });
                    }
                }
            }

        });

    })(jQuery);

    /**
     * Setup function for the plugin
     */
    (function($) {

        'use strict';


        /**
         * A bit smarter split function
         * delimiter can be space, comma, dash or pipe
         * @param {String} val
         * @param {Function|String} [callback]
         * @param {Boolean} [allowSpaceAsDelimiter]
         * @returns {Array|void}
         */
        $.split = function(val, callback, allowSpaceAsDelimiter) {
            // default to true
            allowSpaceAsDelimiter = allowSpaceAsDelimiter === undefined || allowSpaceAsDelimiter === true;
            var pattern = '[,|' + (allowSpaceAsDelimiter ? '\\s' : '') + '-]\\s*',
                regex = new RegExp(pattern, 'g');
            if (typeof callback !== 'function') {
                // return array
                if (!val) {
                    return [];
                }
                var values = [];
                $.each(val.split(callback ? callback : regex),
                    function(i, str) {
                        str = $.trim(str);
                        if (str.length) {
                            values.push(str);
                        }
                    }
                );
                return values;
            } else if (val) {
                // exec callback func on each
                $.each(val.split(regex),
                    function(i, str) {
                        str = $.trim(str);
                        if (str.length) {
                            return callback(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',
                validateOnEvent: false,
                validateOnBlur: true,
                validateCheckboxRadioOnClick: true,
                showHelpOnFocus: true,
                addSuggestions: true,
                modules: '',
                onModulesLoaded: null,
                language: false,
                onSuccess: false,
                onError: false,
                onElementValidate: false
            });

            conf = $.extend(defaultConf, conf || {});

            $(window).trigger('formValidationPluginInit', [conf]);

            if (conf.lang && conf.lang !== 'en') {
                var langModule = 'lang/' + conf.lang + '.js';
                conf.modules += conf.modules.length ? ',' + langModule : langModule;
            }

            // Add validation to forms
            $(conf.form).each(function(i, form) {

                // Make a reference to the config for this form
                form.validationConfig = conf;

                // Trigger jQuery event that we're about to setup validation
                var $form = $(form);
                $form.trigger('formValidationSetup', [$form, conf]);

                // Remove classes and event handlers that might have been
                // added by a previous call to $.validate
                $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(evt) {

                        var $form = $(this),
                            stop = function() {
                                evt.stopImmediatePropagation();
                                return false;
                            };

                        if ($.formUtils.haltValidation) {
                            // pressing several times on submit button while validation is halted
                            return stop();
                        }

                        if ($.formUtils.isLoadingModules) {
                            setTimeout(function() {
                                $form.trigger('submit.validation');
                            }, 200);
                            return stop();
                        }

                        var valid = $form.isValid(conf.language, conf);
                        if ($.formUtils.haltValidation) {
                            // Validation got halted by one of the validators
                            return stop();
                        } else {
                            if (valid && typeof conf.onSuccess === 'function') {
                                var callbackResponse = conf.onSuccess($form);
                                if (callbackResponse === false) {
                                    return stop();
                                }
                            } else if (!valid && typeof conf.onError === 'function') {
                                conf.onError($form);
                                return stop();
                            } else {
                                return valid ? true : stop();
                            }
                        }
                    })
                    .bind('reset.validation', function() {
                        $.formUtils.dialogs.removeAllMessagesAndStyling($form, 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 !== '') {
                $.formUtils.loadModules(conf.modules, null, function() {
                    if (typeof conf.onModulesLoaded === 'function') {
                        conf.onModulesLoaded();
                    }
                    var $form = typeof conf.form === 'string' ? $(conf.form) : conf.form;
                    $.formUtils.$win.trigger('validatorsLoaded', [$form, conf]);
                });
            }
        };

    })(jQuery);

    /**
     * Utility methods and properties attached to $.formUtils
     */
    (function($, window) {

        'use strict';

        var $win = $(window);

        $.formUtils = $.extend($.formUtils || {}, {

            $win: $win,

            /**
             * Default config for $(...).isValid();
             */
            defaultConfig: function() {
                return {
                    ignore: [], // Names of inputs not to be validated even though `validationRuleAttribute` containing the validation rules tells us to
                    errorElementClass: 'error', // Class that will be put on elements which value is invalid
                    successElementClass: 'valid', // Class that will be put on elements that has been validated with success
                    borderColorOnError: '#b94a48', // 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: 'inline', // Can be either "top" or "inline"
                    errorMessageTemplate: {
                        container: '<div class="{errorMessageClass} alert alert-danger">{messages}</div>',
                        messages: '<strong>{errorTitle}</strong><ul>{fields}</ul>',
                        field: '<li>{msg}</li>'
                    },
                    scrollToTopOnError: true,
                    dateFormat: 'yyyy-mm-dd',
                    addValidClassOnAll: false, // whether or not to apply class="valid" even if the input wasn't validated
                    decimalSeparator: '.',
                    inputParentClassOnError: 'has-error', // twitter-bootstrap default class name
                    inputParentClassOnSuccess: 'has-success', // twitter-bootstrap default class name
                    validateHiddenInputs: false, // whether or not hidden inputs should be validated
                    inlineErrorMessageCallback: false,
                    submitErrorMessageCallback: false
                };
            },

            /**
             * Available validators
             */
            validators: {},

            /**
             * Available sanitizers
             */
            sanitizers: {},

            /**
             * 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,

            /**
             * Function for adding a validator
             * @see $.formUtils.addAsyncValidator (async.js)
             * @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;
            },

            /**
             * Function for adding a sanitizer
             * @param {Object} sanitizer
             */
            addSanitizer: function(sanitizer) {
                this.sanitizers[sanitizer.name] = sanitizer;
            },

            /**
             * Warn user via the console if available
             */
            warn: function(msg, fallbackOnAlert) {
                if ('console' in window) {
                    if (typeof window.console.warn === 'function') {
                        window.console.warn(msg);
                    } else if (typeof window.console.log === 'function') {
                        window.console.log(msg);
                    }
                } else if (fallbackOnAlert) {
                    // This is for some old IE version...
                    alert(msg);
                }
            },

            /**
             * Same as input $.fn.val() but also supporting input of typ radio or checkbox
             * @example
             *
             *  $.formUtils.getValue('.myRadioButtons', $('#some-form'));
             *  $.formUtils.getValue($('#some-form').find('.check-boxes'));
             *
             * @param query
             * @param $parent
             * @returns {String|Boolean}
             */
            getValue: function(query, $parent) {
                var $inputs = $parent ? $parent.find(query) : query;
                if ($inputs.length > 0) {
                    var type = $inputs.eq(0).attr('type');
                    if (type === 'radio' || type === 'checkbox') {
                        return $inputs.filter(':checked').val() || '';
                    } else {
                        return $inputs.val() || '';
                    }
                }
                return false;
            },

            /**
             * Validate the value of given element according to the validation rules
             * found in the attribute data-validation. Will return an object representing
             * a validation result, having the props shouldChangeDisplay, isValid and errorMsg
             * @param {jQuery} $elem
             * @param {Object} language ($.formUtils.LANG)
             * @param {Object} conf
             * @param {jQuery} $form
             * @param {String} [eventContext]
             * @return {Object}
             */
            validateInput: function($elem, language, conf, $form, eventContext) {

                conf = conf || $.formUtils.defaultConfig();
                language = language || $.formUtils.LANG;

                if (!$form.length) {
                    $form = $elem.parent();
                }

                var value = this.getValue($elem);

                $elem
                    .valAttr('skipped', false)
                    .one('beforeValidation', function() {
                        // Skip input because its hidden or disabled
                        // Doing this in a callback makes it possible for others to prevent the default
                        // behaviour by binding to the same event and call evt.stopImmediatePropagation()
                        if ($elem.attr('disabled') || (!$elem.is(':visible') && !conf.validateHiddenInputs)) {
                            $elem.valAttr('skipped', 1);
                        }
                    })
                    .trigger('beforeValidation', [value, language, conf]);

                var inputIsOptional = $elem.valAttr('optional') === 'true',
                    skipBecauseItsEmpty = !value && inputIsOptional,
                    validationRules = $elem.attr(conf.validationRuleAttribute),
                    isValid = true,
                    errorMsg = '',
                    result = { isValid: true, shouldChangeDisplay: true, errorMsg: '' };

                // For input type="number", browsers attempt to parse the entered value into a number.
                // If the input is not numeric, browsers handle the situation differently:
                // Chrome 48 simply disallows non-numeric input; FF 44 clears out the input box on blur;
                // Safari 5 parses the entered string to find a leading number.
                // If the input fails browser validation, the browser sets the input value equal to an empty string.
                // Therefore, we cannot distinguish (apart from hacks) between an empty input type="text" and one with a
                // value that can't be parsed by the browser.

                if (!validationRules || skipBecauseItsEmpty || $elem.valAttr('skipped')) {
                    result.shouldChangeDisplay = conf.addValidClassOnAll;
                    return result;
                }

                // Filter out specified characters
                var ignore = $elem.valAttr('ignore');
                if (ignore) {
                    $.each(ignore.split(''), function(i, character) {
                        value = value.replace(new RegExp('\\' + character, 'g'), '');
                    });
                }

                $.split(validationRules, function(rule) {

                    if (rule.indexOf('validate_') !== 0) {
                        rule = 'validate_' + rule;
                    }

                    var validator = $.formUtils.validators[rule];

                    if (validator) {

                        // special change of element for checkbox_group rule
                        if (rule === 'validate_checkbox_group') {
                            // set element to first in group, so error msg attr doesn't need to be set on all elements in group
                            $elem = $form.find('[name="' + $elem.attr('name') + '"]:eq(0)');
                        }

                        if (eventContext !== 'keyup' || validator.validateOnKeyUp) {
                            // A validator can prevent itself from getting triggered on keyup
                            isValid = validator.validatorFunction(value, $elem, conf, language, $form, eventContext);
                        }

                        if (!isValid) {
                            if (conf.validateOnBlur) {
                                $elem.validateOnKeyUp(language, conf);
                            }
                            errorMsg = $.formUtils.dialogs.resolveErrorMessage($elem, validator, rule, conf, language);
                            return false; // break iteration
                        }

                    } else {

                        // todo: Add some validator lookup function and tell immediately which module is missing
                        throw new Error('Using undefined validator "' + rule +
                            '". Maybe you have forgotten to load the module that "' + rule + '" belongs to?');

                    }

                });


                if (isValid === false) {
                    $elem.trigger('validation', false);
                    result.errorMsg = errorMsg;
                    result.isValid = false;
                    result.shouldChangeDisplay = true;
                } else if (isValid === null) {
                    // A validatorFunction returning null means that it's not able to validate
                    // the input at this time. Most probably some async stuff need to gets finished
                    // first and then the validator will re-trigger the validation.
                    result.shouldChangeDisplay = false;
                } else {
                    $elem.trigger('validation', true);
                    result.shouldChangeDisplay = true;
                }

                // Run element validation callback
                if (typeof conf.onElementValidate === 'function' && errorMsg !== null) {
                    conf.onElementValidate(result.isValid, $elem, $form, errorMsg);
                }

                $elem.trigger('afterValidation', [result, eventContext]);

                return result;
            },

            /**
             * Is it a correct date according to given dateFormat. Will return false if not, otherwise
             * an array 0=>year 1=>month 2=>day
             *
             * @param {String} val
             * @param {String} dateFormat
             * @param {Boolean} [addMissingLeadingZeros]
             * @return {Array}|{Boolean}
             */
            parseDate: function(val, dateFormat, addMissingLeadingZeros) {
                var divider = dateFormat.replace(/[a-zA-Z]/gi, '').substring(0, 1),
                    regexp = '^',
                    formatParts = dateFormat.split(divider || null),
                    matches, day, month, year;

                $.each(formatParts, function(i, part) {
                    regexp += (i > 0 ? '\\' + divider : '') + '(\\d{' + part.length + '})';
                });

                regexp += '$';

                if (addMissingLeadingZeros) {
                    var newValueParts = [];
                    $.each(val.split(divider), function(i, part) {
                        if (part.length === 1) {
                            part = '0' + part;
                        }
                        newValueParts.push(part);
                    });
                    val = newValueParts.join(divider);
                }

                matches = val.match(new RegExp(regexp));
                if (matches === null) {
                    return false;
                }

                var findDateUnit = function(unit, formatParts, matches) {
                    for (var i = 0; i < formatParts.length; i++) {
                        if (formatParts[i].substring(0, 1) === unit) {
                            return $.formUtils.parseDateInt(matches[i + 1]);
                        }
                    }
                    return -1;
                };

                month = findDateUnit('m', formatParts, matches);
                day = findDateUnit('d', formatParts, matches);
                year = findDateUnit('y', formatParts, matches);

                if ((month === 2 && day > 28 && (year % 4 !== 0 || year % 100 === 0 && year % 400 !== 0)) ||
                    (month === 2 && day > 29 && (year % 4 === 0 || year % 100 !== 0 && year % 400 === 0)) ||
                    month > 12 || month === 0) {
                    return false;
                }
                if ((this.isShortMonth(month) && day > 30) || (!this.isShortMonth(month) && day > 31) || day === 0) {
                    return false;
                }

                return [year, month, day];
            },

            /**
             * 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
             * @return {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, 10)
             * @return array
             */
            numericRangeCheck: function(value, rangeAllowed) {
                // split by dash
                var range = $.split(rangeAllowed),
                    // min or max
                    minmax = parseInt(rangeAllowed.substr(3), 10);

                if (range.length === 1 && rangeAllowed.indexOf('min') === -1 && rangeAllowed.indexOf('max') === -1) {
                    range = [rangeAllowed, rangeAllowed]; // only a number, checking agains an exact number of characters
                }

                // 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
                    $win.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());
                                        $input.trigger('change');
                                        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);
                                $input.trigger('change');
                                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!',
                requiredField: 'This is a required field',
                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',
                andSpaces: ' and spaces ',
                badInt: 'The input value was not a correct number',
                badSecurityNumber: 'Your social security number was incorrect',
                badUKVatAnswer: 'Incorrect UK VAT Number',
                badUKNin: 'Incorrect UK NIN',
                badUKUtr: 'Incorrect UK UTR 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 (max %s)',
                wrongFileType: 'Only files of type %s is allowed',
                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',
                wrongFileDim: 'Incorrect image dimensions,',
                imageTooTall: 'the image can not be taller than',
                imageTooWide: 'the image can not be wider than',
                imageTooSmall: 'the image was too small',
                min: 'min',
                max: 'max',
                imageRatioNotAccepted: 'Image ratio is not be accepted',
                badBrazilTelephoneAnswer: 'The phone number entered is invalid',
                badBrazilCEPAnswer: 'The CEP entered is invalid',
                badBrazilCPFAnswer: 'The CPF entered is invalid',
                badPlPesel: 'The PESEL entered is invalid',
                badPlNip: 'The NIP entered is invalid',
                badPlRegon: 'The REGON entered is invalid',
                badreCaptcha: 'Please confirm that you are not a bot',
                passwordComplexityStart: 'Password must contain at least ',
                passwordComplexitySeparator: ', ',
                passwordComplexityUppercaseInfo: ' uppercase letter(s)',
                passwordComplexityLowercaseInfo: ' lowercase letter(s)',
                passwordComplexitySpecialCharsInfo: ' special character(s)',
                passwordComplexityNumericCharsInfo: ' numeric character(s)',
                passwordComplexityEnd: '.'
            }
        });

    })(jQuery, window);

    /**
     * File declaring all default validators.
     */
    (function($) {

        /*
         * Validate email
         */
        $.formUtils.addValidator({
            name: 'email',
            validatorFunction: function(email) {

                var emailParts = email.toLowerCase().split('@'),
                    localPart = emailParts[0],
                    domain = emailParts[1];

                if (localPart && domain) {

                    if (localPart.indexOf('"') === 0) {
                        var len = localPart.length;
                        localPart = localPart.replace(/\"/g, '');
                        if (localPart.length !== (len - 2)) {
                            return false; // It was not allowed to have more than two apostrophes
                        }
                    }

                    return $.formUtils.validators.validate_domain.validatorFunction(emailParts[1]) &&
                        localPart.indexOf('.') !== 0 &&
                        localPart.substring(localPart.length - 1, localPart.length) !== '.' &&
                        localPart.indexOf('..') === -1 &&
                        !(/[^\w\+\.\-\#\-\_\~\!\$\&\'\(\)\*\+\,\;\=\:]/.test(localPart));
                }

                return false;
            },
            errorMessage: '',
            errorMessageKey: 'badEmail'
        });

        /*
         * Validate domain name
         */
        $.formUtils.addValidator({
            name: 'domain',
            validatorFunction: function(val) {
                return val.length > 0 &&
                    val.length <= 253 && // Including sub domains
                    !(/[^a-zA-Z0-9]/.test(val.slice(-2))) && !(/[^a-zA-Z0-9]/.test(val.substr(0, 1))) && !(/[^a-zA-Z0-9\.\-]/.test(val)) &&
                    val.split('..').length === 1 &&
                    val.split('.').length > 1;
            },
            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: function(config) {
                if (config.errorMessagePosition === 'top' || typeof config.errorMessagePosition === 'function') {
                    return 'requiredFields';
                } else {
                    return 'requiredField';
                }
            }
        });

        /*
         * 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) {
                    alert('Please add attribute "data-validation-length" to ' + $el[0].nodeName + ' 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],
                        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,
                        sanitize = $el.attr('data-sanitize') || '',
                        isFormattedWithNumeral = sanitize.match(/(^|[\s])numberFormat([\s]|$)/i);

                    if (isFormattedWithNumeral) {
                        if (!window.numeral) {
                            throw new ReferenceError('The data-sanitize value numberFormat cannot be used without the numeral' +
                                ' library. Please see Data Validation in http://www.formvalidator.net for more information.');
                        }
                        //Unformat input first, then convert back to String
                        if (val.length) {
                            val = String(numeral().unformat(val));
                        }
                    }

                    if (allowing.indexOf('number') === -1) {
                        allowing += ',number';
                    }

                    if (allowing.indexOf('negative') === -1 && val.indexOf('-') === 0) {
                        return false;
                    }

                    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 (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.valAttr('allowing'),
                    pattern = '',
                    hasSpaces = false;

                if (additionalChars) {
                    pattern = patternStart + additionalChars + patternEnd;
                    var extra = additionalChars.replace(/\\/g, '');
                    if (extra.indexOf(' ') > -1) {
                        hasSpaces = true;
                        extra = extra.replace(' ', '');
                        extra += language.andSpaces || $.formUtils.LANG.andSpaces;
                    }

                    if (language.badAlphaNumericAndExtraAndSpaces && language.badAlphaNumericAndExtra) {
                        if (hasSpaces) {
                            this.errorMessage = language.badAlphaNumericAndExtraAndSpaces + extra;
                        } else {
                            this.errorMessage = language.badAlphaNumericAndExtra + extra + language.badAlphaNumericExtra;
                        }
                    } else {
                        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) {
                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 = $el.valAttr('format') || conf.dateFormat || 'yyyy-mm-dd',
                    addMissingLeadingZeros = $el.valAttr('require-leading-zero') === 'false';
                return $.formUtils.parseDate(date, dateFormat, addMissingLeadingZeros) !== 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 isValid = true,
                    // get name of element. since it is a checkbox group, all checkboxes will have same name
                    elname = $el.attr('name'),
                    // get checkboxes and count the checked ones
                    $checkBoxes = $('input[type=checkbox][name^="' + elname + '"]', $form),
                    checkedCount = $checkBoxes.filter(':checked').length,
                    // get el attr that specs qty required / allowed
                    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;
                        isValid = false;
                        break;
                        // below min qty
                    case 'min':
                        this.errorMessage = lang.groupCheckedTooFewStart + qtyCheckResults[1] + (lang.groupCheckedTooFewEnd || lang.groupCheckedEnd);
                        isValid = false;
                        break;
                        // above max qty
                    case 'max':
                        this.errorMessage = lang.groupCheckedTooManyStart + qtyCheckResults[1] + (lang.groupCheckedTooManyEnd || lang.groupCheckedEnd);
                        isValid = false;
                        break;
                        // ok
                    default:
                        isValid = true;
                }

                if (!isValid) {
                    var _triggerOnBlur = function() {
                        $checkBoxes.unbind('click', _triggerOnBlur);
                        $checkBoxes.filter('*[data-validation]').validateInputOnBlur(lang, conf, false, 'blur');
                    };
                    $checkBoxes.bind('click', _triggerOnBlur);
                }

                return isValid;
            }
            //   errorMessage : '', // set above in switch statement
            //   errorMessageKey: '' // not used
        });

    })(jQuery);


}));