PageSourceSearch

https://www.loewenherz.at/scripts/jquery.form-validator.js

js loewenherz.at collected 2026-10-02 16:44:54 UTC 59,197 bytes, 1,470 lines download raw bytes

1/**
2* jQuery Form Validator
3* ------------------------------------------
4* Created by Victor Jonsson <http://www.victorjonsson.se>
5*
6* @website http://formvalidator.net/
7* @license Dual licensed under the MIT or GPL Version 2 licenses
8* @version 2.1.27
9*/
10(function($) {
11
12    'use strict';
13
14    /**
15    * Assigns validateInputOnBlur function to elements blur event
16    *
17    * @param {Object} language Optional, will override $.formUtils.LANG
18    * @param {Object} settings Optional, will override the default settings
19    * @return {jQuery}
20    */
21    $.fn.validateOnBlur = function(language, settings) {
22
23        var blurValidationCallback = function() {
24                $(this).validateInputOnBlur(language, settings);
25            };
26
27        this.find('input[data-validation], textarea[data-validation]')
28            .unbind('blur', blurValidationCallback)
29            .bind('blur', blurValidationCallback);
30
31        return this;
32    };
33
34    /**
35    * fade in help message when input gains focus
36    * fade out when input loses focus
37    * <input data-help="The info that I want to display for the user when input is focused" ... />
38    *
39    * @param {String} attrName - Optional, default is data-help
40    * @return {jQuery}
41    */
42    $.fn.showHelpOnFocus = function(attrName) {
43        if(!attrName) {
44            attrName = 'data-validation-help';
45        }
46
47        // Remove previously added event listeners
48        this.find('.has-help-txt')
49                .valAttr('has-keyup-event', false)
50                .valAttr('backend-valid', false)
51                .valAttr('backend-invalid', false)
52                .unbind('focus')
53                .unbind('blur')
54                .removeClass('has-help-txt');
55
56        // Add help text listeners
57        this.find('textarea,input').each(function() {
58            var $element = $(this),
59                className = 'jquery_form_help_' + $element.attr('name'),
60                help = $element.attr(attrName);
61
62            if(help) {
63                $element
64                    .addClass('has-help-txt')
65                    .focus(function() {
66                        var $help = $element.parent().find('.'+className);
67                        if($help.length == 0) {
68                            $help = $('<span />')
69                                        .addClass(className)
70                                        .addClass('help-block') // twitter bs
71                                        .text(help)
72                                        .hide();
73
74                            $element.after($help);
75
76                        }
77                        $help.fadeIn();
78                    })
79                    .blur(function() {
80                        $(this)
81                            .parent()
82                            .find('.'+className)
83                                .fadeOut('slow');
84                    });
85            }
86        });
87
88        return this;
89    };
90
91    /**
92    * Validate single input when it loses focus
93    * shows error message in a span element 
94    * that is appended to the parent element
95    *
96    * @param {Object} [language] Optional, will override $.formUtils.LANG
97    * @param {Object} [config] Optional, will override the default settings
98    * @param {Boolean} [attachKeyupEvent] Optional
99    * @param {String} [eventContext]
100    * @return {jQuery}
101    */
102    $.fn.validateInputOnBlur = function(language, config, attachKeyupEvent, eventContext) {
103        if(attachKeyupEvent === undefined)
104            attachKeyupEvent = true;
105        if(!eventContext)
106            eventContext = 'blur';
107
108        language = $.extend($.formUtils.LANG, language || {});
109        config = $.extend($.formUtils.defaultConfig(), config || {});
110        config.errorMessagePosition = 'element';
111
112        var $element = this,
113
114            // test if there is custom obj to hold element error msg (id = element name + err_msg)
115            elementErrMsgObj = document.getElementById($element.attr('name')+'_err_msg'),
116
117            $form = $element.closest("form"),
118
119            validationRule = $element.attr(config.validationRuleAttribute);
120
121        // Remove possible error style applied by previous validation
122        $element
123            .removeClass(config.errorElementClass)
124            .css('border-color', '')
125            .parent()
126                .find('.'+config.errorMessageClass).remove();
127
128        // Twitter bs
129        $form.find('.has-error').removeClass('has-error');
130        $element.removeClass('valid').parent().removeClass('has-success');
131
132        // if element has custom err msg container, clear it
133        if( elementErrMsgObj != null) {
134            elementErrMsgObj.innerHTML = '';
135        }
136
137        var validation = $.formUtils.validateInput($element, language, config, $form, eventContext);
138
139        if(validation === true) {
140            $element
141                .addClass('valid')
142                .parent()
143                    .addClass('has-success'); // twitter bs
144        } else if(validation === null) {
145            $element
146                .removeClass('valid')
147                .parent()
148                    .removeClass('has-error')
149                    .removeClass('has-success');
150        } else {
151            $element
152                .addClass(config.errorElementClass)
153                .removeClass('valid')
154                .parent()
155                    .addClass('has-error')
156                    .removeClass('has-success'); // twitter bs
157
158            // if element has custom err msg container, use it
159            if( elementErrMsgObj != null) {
160                elementErrMsgObj.innerHTML = validation;
161            } else { // use regular span append
162                var $parent = $element.parent();
163                $parent.append('<span class="'+config.errorMessageClass+' help-block">'+validation+'</span>');
164                $parent.addClass('has-error'); // twitter bs
165            }
166
167            if(config.borderColorOnError !== '') {
168                $element.css('border-color', config.borderColorOnError);
169            }
170
171            if(attachKeyupEvent) {
172                $element.bind('keyup', function() {
173                    $(this).validateInputOnBlur(language, config, false, 'keyup');
174                });
175            }
176        }
177
178        return this;
179    };
180
181    /**
182     * Short hand for fetching/adding/removing element attributes
183     * prefixed with 'data-validation-'
184     *
185     * @param {String} name
186     * @param {String|Boolean} [val]
187     * @return string|undefined
188     * @protected
189     */
190    $.fn.valAttr = function(name, val) {
191        if( val === undefined ) {
192            return this.attr('data-validation-'+name);
193        } else if( val === false || val === null ) {
194            return this.removeAttr('data-validation-'+name);
195        } else {
196            if(name.length > 0) name='-'+name;
197            return this.attr('data-validation'+name, val);
198        }
199    };
200
201    /**
202     * Function that validate all inputs in a form
203     *
204     * @param [language]
205     * @param [config]
206     */
207    $.fn.validateForm = function(language, config) {
208
209        language = $.extend($.formUtils.LANG, language || {});
210        config = $.extend($.formUtils.defaultConfig(), config || {});
211
212        $.formUtils.isValidatingEntireForm = true;
213        $.formUtils.haltValidation = false;
214
215        /**
216         * Adds message to error message stack if not already in the message stack
217         *
218         * @param {String} mess
219         * @para {jQuery} $element
220         */
221        var addErrorMessage = function(mess, $element) {
222            // validate server side will return null as error message before the server is requested
223            if(mess !== null) {
224                if ($.inArray(mess, errorMessages) < 0) {
225                    errorMessages.push(mess);
226                }
227                errorInputs.push($element);
228                $element
229                    .valAttr('current-error', mess)
230                    .removeClass('valid')
231                    .parent()
232                    .removeClass('has-success');
233            }
234        },
235
236        /** Error messages for this validation */
237        errorMessages = [],
238
239        /** Input elements which value was not valid */
240        errorInputs = [],
241
242        /** Form instance */
243        $form = this,
244
245        /**
246         * Tells whether or not to validate element with this name and of this type
247         *
248         * @param {String} name
249         * @param {String} type
250         * @return {Boolean}
251         */
252        ignoreInput = function(name, type) {
253            if (type === 'submit' || type === 'button') {
254                return true;
255            }
256            return $.inArray(name, config.ignore || []) > -1;
257        };
258
259        //
260        // Validate element values
261        //
262        $form.find('input,textarea,select').filter(':not([type="submit"],[type="button"])').each(function() {
263            var $element = $(this);
264            var elementType = $element.attr('type');
265            if (!ignoreInput($element.attr('name'), elementType)) {
266
267                var validation = $.formUtils.validateInput(
268                                $element,
269                                language,
270                                config,
271                                $form,
272                                'submit'
273                            );
274
275                if(validation !== true) {
276                    addErrorMessage(validation, $element);
277                } else {
278                    $element
279                        .valAttr('current-error', false)
280                        .addClass('valid')
281                        .parent()
282                            .addClass('has-success');
283                }
284            }
285
286        });
287
288        //
289        // Reset style and remove error class
290        //
291        $form.find('.has-error').removeClass('has-error');
292        $form.find('input,textarea,select')
293            .css('border-color', '')
294            .removeClass(config.errorElementClass);
295
296        //
297        // Remove possible error messages from last validation
298        //
299        $('.' + $.split(config.errorMessageClass, ' ').join('.')).remove();
300        $('.'+config.errorMessageClass).remove();
301
302        //
303        // Run validation callback
304        //
305        if( typeof config.onValidate == 'function' ) {
306            var resp = config.onValidate($form);
307            if( resp && resp.element && resp.message ) {
308                addErrorMessage(resp.message, resp.element);
309            }
310        }
311
312        //
313        // Validation failed
314        //
315        if (!$.formUtils.haltValidation && errorInputs.length > 0) {
316
317            // Reset form validation flag
318            $.formUtils.isValidatingEntireForm = false;
319
320            // Apply error style to invalid inputs
321            $.each(errorInputs, function(i, $input) {
322                if (config.borderColorOnError !== '') {
323                    $input.css('border-color', config.borderColorOnError);
324                }
325                $input
326                    .addClass(config.errorElementClass)
327                    .parent()
328                        .addClass('has-error');
329            });
330
331            // display all error messages in top of form
332            if (config.errorMessagePosition === 'top') {
333                var messages = '<strong>' + language.errorTitle + '</strong>';
334                $.each(errorMessages, function(i, mess) {
335                    messages += '<br />* ' + mess;
336                });
337
338                // using div instead of P gives better control of css display properties
339                $form.children().eq(0).before('<div class="' + config.errorMessageClass + ' alert alert-danger">' + messages + '</div>');
340                if(config.scrollToTopOnError) {
341                    $(window).scrollTop($form.offset().top - 20);
342                }
343            }
344
345            // Display error message below input field
346            else {
347                $.each(errorInputs, function(i, $input) {
348                    var $parent = $input.parent(),
349                        $errorSpan = $parent.find('span[class='+config.errorMessageClass+']');
350                    if ($errorSpan.length > 0) {
351                        $errorSpan.text(', '+$input.valAttr('current-error'));
352                    } else {
353                        $parent.append('<span class="'+config.errorMessageClass+' help-block">' + $input.valAttr('current-error') + '</span>');
354                    }
355                });
356            }
357            return false;
358        }
359
360        // Reset form validation flag
361        $.formUtils.isValidatingEntireForm = false;
362
363        return !$.formUtils.haltValidation;
364    };
365
366    /**
367    * Plugin for displaying input length restriction
368    */
369    $.fn.restrictLength = function(maxLengthElement) {
370        new $.formUtils.lengthRestriction(this, maxLengthElement);
371        return this;
372    };
373
374    /**
375     * Add suggestion dropdown to inputs having data-suggestions with a comma
376     * separated string with suggestions
377     * @param {Array} [settings]
378     * @returns {jQuery}
379     */
380    $.fn.addSuggestions = function(settings) {
381        var sugs = false;
382        this.find('input').each(function() {
383            var $field = $(this);
384
385            sugs = $.split($field.attr('data-suggestions'));
386
387            if( sugs.length > 0 && !$field.hasClass('has-suggestions') ) {
388                $.formUtils.suggest($field, sugs, settings);
389                $field.addClass('has-suggestions');
390            }
391        });
392        return this;
393    };
394
395    /**
396     * A bit smarter split function
397     * @param {String} val
398     * @param {Function|String} [func]
399     * @param {String} [delim]
400     * @returns {Array|void}
401     */
402    $.split = function(val, func, delim) {
403        if( typeof func != 'function' ) {
404            // return string
405            if( !val )
406                return [];
407            var values = [];
408            $.each(val.split(func ? func:','), function(i,str) {
409                str = $.trim(str);
410                if( str.length )
411                    values.push(str);
412            });
413            return values;
414        } else if( val ) {
415            // use callback on each
416            if( !delim )
417                delim = ',';
418            $.each(val.split(delim), function(i, str) {
419                str = $.trim(str);
420                if( str.length )
421                    return func(str, i);
422            });
423        }
424    };
425
426    /**
427     * Short hand function that makes the validation setup require less code
428     * @param config
429     */
430    $.validate = function(config) {
431        config = $.extend({
432            form : 'form',
433            validateOnBlur : true,
434            showHelpOnFocus : true,
435            addSuggestions : true,
436            modules : '',
437            onModulesLoaded : null,
438            language : false,
439            onSuccess : false,
440            onError : false
441        }, config || {});
442
443        var formSubmitCallback = function() {
444            var $form = $(this);
445            if($.formUtils.isLoadingModules) {
446                setTimeout(function() {
447                    $form.trigger('submit');
448                }, 200);
449                return false;
450            }
451            var valid = $(this).validateForm(config.language, config);
452            if( valid && typeof config.onSuccess == 'function') {
453                var callbackResponse = config.onSuccess($form);
454                if( callbackResponse === false )
455                    return false;
456            } else if ( !valid && typeof config.onError == 'function' ) {
457                config.onError($form);
458                return false;
459            } else {
460                return valid;
461            }
462        };
463
464        // Remove all event listeners previously added
465        $('form').unbind('submit', formSubmitCallback);
466
467        // Add validation to forms
468        $.split(config.form, function(formQuery) {
469
470            var $form  = $(formQuery);
471
472            // Validate when submitted
473            $form.bind('submit', formSubmitCallback);
474
475            if( config.showHelpOnFocus ) {
476                $form.showHelpOnFocus();
477            }
478            if( config.addSuggestions ) {
479                $form.addSuggestions();
480            }
481            if( config.validateOnBlur ) {
482                $form.validateOnBlur(config.language, config);
483            }
484        });
485
486        if( config.modules != '' ) {
487            if( typeof config.onModulesLoaded == 'function' ) {
488                $.formUtils.on('load', function() {
489                    config.onModulesLoaded();
490                });
491            }
492            $.formUtils.loadModules(config.modules);
493        }
494    };
495
496    /**
497     * @deprecated
498     * @param {Object} config
499     */
500    $.validationSetup = function(config) {
501        if( typeof console != 'undefined' && console.warn ) {
502            window.console.warn('Using deprecated function $.validationSetup, pls use $.validate instead');
503        }
504        $.validate(config);
505    };
506
507    /**
508     * Object containing utility methods for this plugin
509     */
510    $.formUtils = {
511
512        /**
513         * Default config for $(...).validateForm();
514         */
515        defaultConfig :  function() {
516            return {
517                ignore : [], // Names of inputs not to be validated even though node attribute containing the validation rules tells us to
518                errorElementClass : 'error', // Class that will be put on elements which value is invalid
519                borderColorOnError : 'red', // Border color of elements which value is invalid, empty string to not change border color
520                errorMessageClass : 'form-error', // class name of div containing error messages when validation fails
521                validationRuleAttribute : 'data-validation', // name of the attribute holding the validation rules
522                validationErrorMsgAttribute : 'data-validation-error-msg', // define custom err msg inline with element
523                errorMessagePosition : 'element', // Can be either "top" or "element"
524                scrollToTopOnError : true,
525                dateFormat : 'yyyy-mm-dd',
526                addValidClassOnAll : false, // whether or not to apply class="valid" even if the input wasn't validated
527                decimalSeparator : '.'
528            }
529        },
530
531        /**
532        * Available validators
533        */
534        validators : {},
535
536        /**
537         * Events triggered by form validator
538         */
539        _events : {load : [], valid: [], invalid:[]},
540
541        /**
542         * Setting this property to true during validation will
543         * stop further validation from taking place and form will
544         * not be sent
545         */
546        haltValidation : false,
547
548        /**
549         * This variable will be true $.fn.validateForm() is called
550         * and false when $.fn.validateOnBlur is called
551         */
552        isValidatingEntireForm : false,
553
554        /**
555        * Function for adding a validator
556        * @param {Object} validator
557        */
558        addValidator : function(validator) {
559            // prefix with "validate_" for backward compatibility reasons
560            var name = validator.name.indexOf('validate_') === 0 ? validator.name : 'validate_'+validator.name;
561            if( validator.validateOnKeyUp === undefined )
562                validator.validateOnKeyUp = true;
563            this.validators[name] = validator;
564        },
565
566        /**
567         * @param {String} evt
568         * @param {Function} callback
569         */
570        on : function(evt, callback) {
571            // Why not use $(document).bind('validators.loaded', func);
572            if( this._events[evt] === undefined )
573                this._events[evt] = [];
574            this._events[evt].push(callback);
575        },
576
577        /**
578         * @param {String} evt
579         * @param [argA]
580         * @param [argB]
581         */
582        trigger : function(evt, argA, argB) {
583            $.each(this._events[evt] || [], function(i, func) {
584                func(argA, argB);
585            });
586        },
587
588        /**
589         * @ {Boolean}
590         */
591        isLoadingModules : false,
592
593        loadedModules : {},
594
595        /**
596        * @example
597        *  $.formUtils.loadModules('date, security.dev');
598        *
599        * Will load the scripts date.js and security.dev.js from the
600        * directory where this script resides. If you want to load
601        * the modules from another directory you can use the
602        * path argument.
603        *
604        * The script will be cached by the browser unless the module
605        * name ends with .dev
606        *
607        * @param {String} modules - Comma separated string with module file names (no directory nor file extension)
608        * @param {String} [path] - Optional, path where the module files is located if their not in the same directory as the core modules
609        * @param {Boolean} [fireEvent] - Optional, whether or not to fire event 'load' when modules finished loading
610        */
611        loadModules : function(modules, path, fireEvent) {
612
613            if( fireEvent === undefined )
614                fireEvent = true;
615
616            if( $.formUtils.isLoadingModules ) {
617                setTimeout(function() {
618                    $.formUtils.loadModules(modules, path, fireEvent);
619                });
620                return;
621            }
622
623            var hasLoadedAnyModule = false;
624
625            var loadModuleScripts = function(modules, path) {
626                var moduleList = $.split(modules),
627                    numModules = moduleList.length,
628                    moduleLoadedCallback = function() {
629                        numModules--;
630                        if( numModules == 0 ) {
631                            $.formUtils.isLoadingModules = false;
632                            if( fireEvent && hasLoadedAnyModule ) {
633                                $.formUtils.trigger('load', path);
634                            }
635                        }
636                    };
637
638                if( numModules > 0 ) {
639                    $.formUtils.isLoadingModules = true;
640                }
641
642                var cacheSuffix = '?__='+( new Date().getTime() ),
643                    appendToElement = document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0];
644
645                $.each(moduleList, function(i, modName) {
646                    modName = $.trim(modName);
647                    if( modName.length == 0 ) {
648                        moduleLoadedCallback();
649                    }
650                    else {
651                        var scriptUrl = path + modName + (modName.substr(-3) == '.js' ? '':'.js'),
652                            script = document.createElement('SCRIPT');
653
654                        if( scriptUrl in $.formUtils.loadedModules ) {
655                            // already loaded
656                            moduleLoadedCallback();
657                        }
658                        else {
659
660                            // Remember that this script is loaded
661                            $.formUtils.loadedModules[scriptUrl] = 1;
662                            hasLoadedAnyModule = true;
663
664                            // Load the script
665                            script.type = 'text/javascript';
666                            script.onload = moduleLoadedCallback;
667                            script.src = scriptUrl + ( scriptUrl.substr(-7) == '.dev.js' ? cacheSuffix:'' );
668                            script.onreadystatechange = function() {
669                                // IE 7 fix
670                                if( this.readyState == 'complete' ) {
671                                    moduleLoadedCallback();
672                                }
673                            };
674                            appendToElement.appendChild( script );
675                        }
676                    }
677                });
678            };
679
680            if( path ) {
681                loadModuleScripts(modules, path);
682            } else {
683                var findScriptPathAndLoadModules = function() {
684                    var foundPath = false;
685                    $('script').each(function() {
686                        if( this.src ) {
687                            var scriptName = this.src.substr(this.src.lastIndexOf('/')+1, this.src.length);
688                            if(scriptName.indexOf('jquery.form-validator.js') > -1 || scriptName.indexOf('jquery.form-validator.min.js') > -1) {
689                                foundPath = this.src.substr(0, this.src.lastIndexOf('/')) + '/';
690                                if( foundPath == '/' )
691                                    foundPath = '';
692                                return false;
693                            }
694                        }
695                    });
696
697                    if( foundPath !== false) {
698                        loadModuleScripts(modules, foundPath);
699                        return true;
700                    }
701                    return false;
702                };
703
704                if( !findScriptPathAndLoadModules() ) {
705                    $(findScriptPathAndLoadModules);
706                }
707            }
708        },
709
710        /**
711        * Validate the value of given element according to the validation rules
712        * found in the attribute data-validation. Will return true if valid,
713        * error message otherwise
714        *
715        * @param {jQuery} $element
716        * @param {Object} language ($.formUtils.LANG)
717        * @param {Object} config
718        * @param {jQuery} $form
719        * @param {String} [eventContext]
720        * @return {String|Boolean}
721        */
722        validateInput : function($element, language, config, $form, eventContext) {
723
724            var value = $.trim( $element.val() || ''),
725                optional = $element.valAttr('optional'),
726
727                // test if a checkbox forces this element to be validated
728                validationDependsOnCheckedInput = false,
729                validationDependentInputIsChecked = false,
730                validateIfCheckedElement = false,
731
732                // get value of this element's attribute "... if-checked"
733                validateIfCheckedElementName = $element.valAttr("if-checked");
734
735            // make sure we can proceed
736            if (validateIfCheckedElementName != null) {
737
738                // Set the boolean telling us that the validation depends
739                // on another input being checked
740                validationDependsOnCheckedInput = true;
741
742                // select the checkbox type element in this form
743                validateIfCheckedElement = $form.find('input[name="' + validateIfCheckedElementName + '"]');
744
745                // test if it's property "checked" is checked
746                if ( validateIfCheckedElement.prop('checked') ) {
747                    // set value for validation checkpoint
748                    validationDependentInputIsChecked = true;
749                }
750            }
751
752            // validation checkpoint
753            // if empty AND optional attribute is present
754            // OR depending on a checkbox being checked AND checkbox is checked, return true
755            if ((!value && optional === 'true') || (validationDependsOnCheckedInput && !validationDependentInputIsChecked)) {
756                return config.addValidClassOnAll ? true:null;
757            }
758
759            var validationRules = $element.attr(config.validationRuleAttribute),
760
761                // see if form element has inline err msg attribute
762                validationErrorMsg = true;
763
764            if( !validationRules ) {
765                return config.addValidClassOnAll ? true:null;
766            }
767
768            $.split(validationRules, function(rule) {
769                if( rule.indexOf('validate_') !== 0 ) {
770                    rule = 'validate_' + rule;
771                }
772
773                var validator = $.formUtils.validators[rule];
774
775                if( validator && typeof validator['validatorFunction'] == 'function' ) {
776                    // special change of element for checkbox_group rule
777                    if ( rule == 'validate_checkbox_group' ) {
778                        // set element to first in group, so error msg is set only once
779                            $element = $("[name='"+$element.attr('name')+"']:eq(0)");
780                    }
781
782                    var isValid = true;
783                    if( eventContext != 'keyup' || validator.validateOnKeyUp ) {
784                        isValid = validator.validatorFunction(value, $element, config, language, $form);
785                    }
786
787                    if(!isValid) {
788                        validationErrorMsg =  $element.attr(config.validationErrorMsgAttribute);
789                        if( !validationErrorMsg ) {
790                            validationErrorMsg = language[validator.errorMessageKey];
791                            if( !validationErrorMsg )
792                                validationErrorMsg = validator.errorMessage;
793                        }
794                        return false; // breaks the iteration
795                    }
796
797                } else {
798                    console.warn('Using undefined validator "'+rule+'"');
799                }
800
801            }, ' ');
802
803            if( typeof validationErrorMsg == 'string' ) {
804                return validationErrorMsg;
805            } else {
806                return true;
807            }
808        },
809
810       /**
811        * Is it a correct date according to given dateFormat. Will return false if not, otherwise
812        * an array 0=>year 1=>month 2=>day
813        *
814        * @param {String} val
815        * @param {String} dateFormat
816        * @return {Array}|{Boolean}
817        */
818        parseDate : function(val, dateFormat) {
819            var divider = dateFormat.replace(/[a-zA-Z]/gi, '').substring(0,1),
820                regexp = '^',
821                formatParts = dateFormat.split(divider),
822                matches, day, month, year;
823
824            $.each(formatParts, function(i, part) {
825               regexp += (i > 0 ? '\\'+divider:'') + '(\\d{'+part.length+'})';
826            });
827
828            regexp += '$';
829            
830            matches = val.match(new RegExp(regexp));
831            if (matches === null) {
832                return false;
833            }
834        
835            var findDateUnit = function(unit, formatParts, matches) {
836                for(var i=0; i < formatParts.length; i++) {
837                    if(formatParts[i].substring(0,1) === unit) {
838                        return $.formUtils.parseDateInt(matches[i+1]);
839                    }
840                }
841                return -1;
842            };
843        
844            month = findDateUnit('m', formatParts, matches);
845            day = findDateUnit('d', formatParts, matches);
846            year = findDateUnit('y', formatParts, matches);
847        
848            if ((month === 2 && day > 28 && (year % 4 !== 0  || year % 100 === 0 && year % 400 !== 0)) 
849            	|| (month === 2 && day > 29 && (year % 4 === 0 || year % 100 !== 0 && year % 400 === 0))
850            	|| month > 12 || month === 0) {
851                return false;
852            }
853            if ((this.isShortMonth(month) && day > 30) || (!this.isShortMonth(month) && day > 31) || day === 0) {
854                return false;
855            }
856        
857            return [year, month, day];
858        },
859
860       /**
861        * skum fix. är talet 05 eller lägre ger parseInt rätt int annars får man 0 när man kör parseInt?
862        *
863        * @param {String} val
864        * @param {Number}
865        */
866        parseDateInt : function(val) {
867            if (val.indexOf('0') === 0) {
868                val = val.replace('0', '');
869            }
870            return parseInt(val,10);
871        },
872
873        /**
874        * Has month only 30 days?
875        *
876        * @param {Number} m
877        * @return {Boolean}
878        */
879        isShortMonth : function(m) {
880            return (m % 2 === 0 && m < 7) || (m % 2 !== 0 && m > 7);
881        },
882
883       /**
884        * Restrict input length
885        *
886        * @param {jQuery} $inputElement Jquery Html object
887        * @param {jQuery} $maxLengthElement jQuery Html Object
888        * @return void
889        */
890        lengthRestriction : function($inputElement, $maxLengthElement) {
891                // read maxChars from counter display initial text value
892           var maxChars = parseInt($maxLengthElement.text(),10),
893
894               // internal function does the counting and sets display value
895               countCharacters = function() {
896                   var numChars = $inputElement.val().length;
897                   if(numChars > maxChars) {
898                       // get current scroll bar position
899                       var currScrollTopPos = $inputElement.scrollTop();
900                       // trim value to max length
901                       $inputElement.val($inputElement.val().substring(0, maxChars));
902                       $inputElement.scrollTop(currScrollTopPos);
903                   }
904                   // set counter text
905                   $maxLengthElement.text(maxChars - numChars);
906               };
907
908           // bind events to this element
909           // setTimeout is needed, cut or paste fires before val is available
910           $($inputElement).bind('keydown keyup keypress focus blur',  countCharacters )
911               .bind('cut paste', function(){ setTimeout(countCharacters, 100); } ) ;
912
913           // count chars on pageload, if there are prefilled input-values
914           $(document).bind("ready", countCharacters);
915        },
916
917        /**
918        * Test numeric against allowed range
919        *
920        * @param $value int
921        * @param $rangeAllowed str; (1-2, min1, max2)
922        * @return array 
923        */
924        numericRangeCheck : function(value, rangeAllowed) 
925        {
926           // split by dash
927           var range = $.split(rangeAllowed, '-');
928           // min or max
929           var minmax = parseInt(rangeAllowed.substr(3),10)
930           // range ?
931           if (range.length == 2 && (value < parseInt(range[0],10) || value > parseInt(range[1],10) ) )
932           {   return [ "out", range[0], range[1] ] ; } // value is out of range
933           else if (rangeAllowed.indexOf('min') === 0 && (value < minmax ) ) // min
934                {  return ["min", minmax]; } // value is below min
935                else if (rangeAllowed.indexOf('max') === 0 && (value > minmax ) ) // max
936                     {   return ["max", minmax]; } // value is above max
937           else { return [ "ok" ] ; } // value is in allowed range
938        },
939
940
941        _numSuggestionElements : 0,
942        _selectedSuggestion : null,
943        _previousTypedVal : null,
944
945        /**
946         * Utility function that can be used to create plugins that gives
947         * suggestions when inputs is typed into
948         * @param {jQuery} $element
949         * @param {Array} suggestions
950         * @param {Object} settings - Optional
951         * @return {jQuery}
952         */
953        suggest : function($element, suggestions, settings) {
954            var config =  {
955                css : {
956                    maxHeight: '150px',
957                    background: '#FFF',
958                    lineHeight:'150%',
959                    textDecoration : 'underline',
960                    overflowX : 'hidden',
961                    overflowY : 'auto',
962                    border : '#CCC solid 1px',
963                    borderTop : 'none',
964                    cursor: 'pointer'
965                },
966                activeSuggestionCSS : {
967                    background : '#E9E9E9'
968                }
969            };
970
971            if(settings)
972                $.extend(config, settings);
973
974            config.css['position'] = 'absolute';
975            config.css['z-index'] = 9999;
976            $element.attr('autocomplete', 'off');
977
978            this._numSuggestionElements++;
979
980            var onSelectSuggestion = function($el) {
981                var suggestionId = $el.valAttr('suggestion-nr');
982                $.formUtils._selectedSuggestion = null;
983                $.formUtils._previousTypedVal = null;
984                $('.jquery-form-suggestion-'+suggestionId).fadeOut('fast');
985            };
986
987            $element
988                .data('suggestions', suggestions)
989                .valAttr('suggestion-nr', this._numSuggestionElements)
990                .bind('focus', function() {
991                    $(this).trigger('keyup');
992                    $.formUtils._selectedSuggestion = null;
993                })
994                .bind('keyup', function() {
995                    var $input = $(this),
996                        foundSuggestions = [],
997                        val = $.trim($input.val()).toLocaleLowerCase();
998
999                    if(val == $.formUtils._previousTypedVal) {
1000                        return;
1001                    }
1002                    else {
1003                        $.formUtils._previousTypedVal = val;
1004                    }
1005
1006                    var hasTypedSuggestion = false,
1007                        suggestionId = $input.valAttr('suggestion-nr'),
1008                        $suggestionContainer = $('.jquery-form-suggestion-'+suggestionId);
1009
1010                    $suggestionContainer.scrollTop(0);
1011
1012                    // Find the right suggestions
1013                    if(val != '') {
1014                        var findPartial = val.length > 2;
1015                        $.each($input.data('suggestions'), function(i, suggestion) {
1016                            var lowerCaseVal = suggestion.toLocaleLowerCase();
1017                            if( lowerCaseVal == val ) {
1018                                foundSuggestions.push('<strong>'+suggestion+'</strong>');
1019                                hasTypedSuggestion = true;
1020                                return false;
1021                            } else if(lowerCaseVal.indexOf(val) === 0 || (findPartial && lowerCaseVal.indexOf(val) > -1)) {
1022                                foundSuggestions.push(suggestion.replace(new RegExp(val, 'gi'), '<strong>$&</strong>'));
1023                            }
1024                        });
1025                    }
1026
1027                    // Hide suggestion container
1028                    if(hasTypedSuggestion || (foundSuggestions.length == 0 && $suggestionContainer.length > 0)) {
1029                        $suggestionContainer.hide();
1030                    }
1031
1032                    // Create suggestion container if not already exists
1033                    else if(foundSuggestions.length > 0 && $suggestionContainer.length == 0) {
1034                        $suggestionContainer = $('<div></div>').css(config.css).appendTo('body');
1035                        $suggestionContainer.addClass('jquery-form-suggestions');
1036                        $suggestionContainer.addClass('jquery-form-suggestion-'+suggestionId);
1037                    }
1038
1039                    // Show hidden container
1040                    else if(foundSuggestions.length > 0 && !$suggestionContainer.is(':visible')) {
1041                        $suggestionContainer.show();
1042                    }
1043
1044                    // add suggestions
1045                    if(foundSuggestions.length > 0 && val.length != foundSuggestions[0].length) {
1046
1047                        // put container in place every time, just in case
1048                        var offset = $input.offset();
1049                        $suggestionContainer.css({
1050                            width : $input.outerWidth(),
1051                            left : offset.left + 'px',
1052                            top : (offset.top + $input.outerHeight()) +'px'
1053                        });
1054
1055                        // Add suggestions HTML to container
1056                        $suggestionContainer.html('');
1057                        $.each(foundSuggestions, function(i, text) {
1058                            $('<div></div>')
1059                                .append(text)
1060                                .css({
1061                                    overflow: 'hidden',
1062                                    textOverflow : 'ellipsis',
1063                                    whiteSpace : 'nowrap',
1064                                    padding: '5px'
1065                                })
1066                                .addClass('form-suggest-element')
1067                                .appendTo($suggestionContainer)
1068                                .click(function() {
1069                                    $input.focus();
1070                                    $input.val( $(this).text() );
1071                                    onSelectSuggestion($input);
1072                                });
1073                        });
1074                    }
1075                })
1076                .bind('keydown', function(e) {
1077                    var code = (e.keyCode ? e.keyCode : e.which),
1078                        suggestionId,
1079                        $suggestionContainer,
1080                        $input = $(this);
1081
1082                    if(code == 13 && $.formUtils._selectedSuggestion !== null) {
1083                        suggestionId = $input.valAttr('suggestion-nr');
1084                        $suggestionContainer = $('.jquery-form-suggestion-'+suggestionId);
1085                        if($suggestionContainer.length > 0) {
1086                            var newText = $suggestionContainer.find('div').eq($.formUtils._selectedSuggestion).text();
1087                            $input.val(newText);
1088                            onSelectSuggestion($input);
1089                            e.preventDefault();
1090                        }
1091                    }
1092                    else {
1093                        suggestionId = $input.valAttr('suggestion-nr');
1094                        $suggestionContainer = $('.jquery-form-suggestion-'+suggestionId);
1095                        var $suggestions = $suggestionContainer.children();
1096                        if($suggestions.length > 0 && $.inArray(code, [38,40]) > -1) {
1097                            if(code == 38) { // key up
1098                                if($.formUtils._selectedSuggestion === null)
1099                                    $.formUtils._selectedSuggestion = $suggestions.length-1;
1100                                else
1101                                    $.formUtils._selectedSuggestion--;
1102                                if($.formUtils._selectedSuggestion < 0)
1103                                    $.formUtils._selectedSuggestion = $suggestions.length-1;
1104                            }
1105                            else if(code == 40) { // key down
1106                                if($.formUtils._selectedSuggestion === null)
1107                                    $.formUtils._selectedSuggestion = 0;
1108                                else
1109                                    $.formUtils._selectedSuggestion++;
1110                                if($.formUtils._selectedSuggestion > ($suggestions.length-1))
1111                                    $.formUtils._selectedSuggestion = 0;
1112
1113                            }
1114
1115                            // Scroll in suggestion window
1116                            var containerInnerHeight = $suggestionContainer.innerHe
1116ight(),
1117                                containerScrollTop = $suggestionContainer.scrollTop(),
1118                                suggestionHeight = $suggestionContainer.children().eq(0).outerHeight(),
1119                                activeSuggestionPosY = suggestionHeight * ($.formUtils._selectedSuggestion);
1120
1121                            if( activeSuggestionPosY < containerScrollTop || activeSuggestionPosY > (containerScrollTop+containerInnerHeight)) {
1122                                $suggestionContainer.scrollTop( activeSuggestionPosY );
1123                            }
1124
1125                            $suggestions
1126                                .removeClass('active-suggestion')
1127                                .css('background', 'none')
1128                                .eq($.formUtils._selectedSuggestion)
1129                                    .addClass('active-suggestion')
1130                                    .css(config.activeSuggestionCSS);
1131
1132                            e.preventDefault();
1133                            return false;
1134                        }
1135                    }
1136                })
1137                .bind('blur', function() {
1138                    onSelectSuggestion($(this));
1139                });
1140
1141            return $element;
1142        },
1143
1144       /**
1145        * Error dialogs
1146        *
1147        * @var {Object}
1148        */
1149        LANG : {
1150            errorTitle : 'Form submission failed!',
1151            requiredFields : 'Bitte dieses Feld ausf&uuml;llen',
1152            badTime : 'You have not given a correct time',
1153            badEmail : 'Bitte eine E-Mail-Adresse eintragen (in Kleinbuchstaben)',
1154            badTelephone : 'You have not given a correct phone number',
1155            badSecurityAnswer : 'You have not given a correct answer to the security question',
1156            badDate : 'You have not given a correct date',
1157            lengthBadStart : 'Bitte eine Eingabe zwischen ',
1158            lengthBadEnd : ' Zeichen',
1159            lengthTooLongStart : 'Diese Eingabe ist l&auml;nger als ',
1160            lengthTooShortStart : 'Diese Eingabe ist k&uuml;rzer als ',
1161            notConfirmed : 'Diese Eingabe stimmt nicht &uuml;berein',
1162            badDomain : 'Incorrect domain value',
1163            badUrl : 'The answer you gave was not a correct URL',
1164            badCustomVal : 'You gave an incorrect answer',
1165            badInt : 'Die Eingabe muss eine Zahl sein',
1166            badSecurityNumber : 'Your social security number was incorrect',
1167            badUKVatAnswer : 'Incorrect UK VAT Number',
1168            badStrength : 'The password isn\'t strong enough',
1169            badNumberOfSelectedOptionsStart : 'You have to choose at least ',
1170            badNumberOfSelectedOptionsEnd : ' answers',
1171            badAlphaNumeric : 'Die Eingabe darf nur Buchstaben und Zahlen enthalten, keine Sonderzeichen wie &auml;, &ouml;, &uuml;, &szlig;',
1172            badAlphaNumericExtra: ' and ',
1173            wrongFileSize : 'The file you are trying to upload is too large',
1174            wrongFileType : 'The file you are trying to upload is of wrong type',
1175            groupCheckedRangeStart : 'Please choose between ',
1176            groupCheckedTooFewStart : 'Bitte w&auml;hle mindestens ',
1177            groupCheckedTooManyStart : 'Please choose a maximum of ',           
1178            groupCheckedEnd : ' M&ouml;glichkeit'
1179        }
1180    };
1181
1182
1183    /* * * * * * * * * * * * * * * * * * * * * *
1184      CORE VALIDATORS
1185    * * * * * * * * * * * * * * * * * * * * */
1186
1187
1188    /*
1189    * Validate email
1190    */
1191    $.formUtils.addValidator({
1192        name : 'email',
1193        validatorFunction : function(email) {
1194
1195            var emailParts = email.split('@');
1196            if( emailParts.length == 2 ) {
1197                return $.formUtils.validators.validate_domain.validatorFunction(emailParts[1]) &&
1198                        !(/[^a-zA-Z0-9_\+\.\-]/.test(emailParts[0]));
1199            }
1200
1201            return false;
1202        },
1203        errorMessage : '',
1204        errorMessageKey : 'badEmail'
1205    });
1206
1207    /*
1208    * Validate domain name
1209    */
1210    $.formUtils.addValidator({
1211    name: 'domain',
1212    validatorFunction: function(val, $input) {
1213        if (typeof val !== 'string') return false;
1214
1215        val = val.trim().toLowerCase();
1216
1217        // Regex to match modern domain names
1218        const domainRegex = /^(?!-)[a-z0-9-]{1,63}(?<!-)(\.(?!-)[a-z0-9-]{1,63}(?<!-))*\.[a-z]{2,}$/;
1219
1220        const isValid = domainRegex.test(val);
1221
1222        if (isValid && typeof $input !== 'undefined') {
1223            $input.val(val); // Optional: normalize input
1224        }
1225
1226        return isValid;
1227    },
1228    errorMessage: 'Bitte trage einen korrekten Domain Namen ein.',
1229    errorMessageKey: 'badDomain'
1230});
1231
1232    /*
1233    * Validate required
1234    */
1235    $.formUtils.addValidator({
1236        name : 'required',
1237        validatorFunction : function(val, $el) {
1238            return $el.attr('type') == 'checkbox' ? $el.is(':checked') : $.trim(val) !== '';
1239        },
1240        errorMessage : '',
1241        errorMessageKey: 'requiredFields'
1242    });
1243
1244    /*
1245    * Validate length range
1246    */
1247    $.formUtils.addValidator({
1248        name : 'length',
1249        validatorFunction : function(val, $el, config, lang) {
1250            var lengthAllowed = $el.valAttr('length'),
1251                type = $el.attr('type');
1252
1253            if(lengthAllowed == undefined) {
1254                var elementType = $el.get(0).nodeName;
1255                alert('Please add attribute "data-validation-length" to '+elementType+' named '+$el.attr('name'));
1256                return true;
1257            }
1258
1259            // check if length is above min, below max or within range.
1260            var len = type == 'file' && $el.get(0).files !== undefined ? $el.get(0).files.length : val.length,
1261                lengthCheckResults = $.formUtils.numericRangeCheck(len, lengthAllowed),
1262                checkResult;
1263
1264            switch(lengthCheckResults[0] )
1265            {   // outside of allowed range
1266                case "out":
1267                    this.errorMessage = lang.lengthBadStart + lengthAllowed + lang.lengthBadEnd;
1268                    checkResult = false;
1269                    break;
1270                // too short
1271                case "min":
1272                    this.errorMessage = lang.lengthTooShortStart + lengthCheckResults[1] + lang.lengthBadEnd;
1273                    checkResult = false;
1274                    break;
1275                // too long
1276                case "max":
1277                    this.errorMessage = lang.lengthTooLongStart + lengthCheckResults[1] + lang.lengthBadEnd;
1278                    checkResult = false;
1279                    break;
1280                // ok
1281                default:
1282                    checkResult = true;
1283            }
1284            
1285            return checkResult;
1286        },
1287        errorMessage : '',
1288        errorMessageKey: ''
1289    });
1290
1291    /*
1292    * Validate url
1293    */
1294    $.formUtils.addValidator({
1295        name : 'url',
1296        validatorFunction : function(url) {
1297            // written by Scott Gonzalez: http://projects.scottsplayground.com/iri/ but added support for arrays in the url ?arg[]=sdfsdf
1298            var urlFilter = /^(https|http|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\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])([a-z]|\d|-|\.|_|~|[\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])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|\[|\]|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i;
1299            if( urlFilter.test(url) ) {
1300                var domain = url.split('://')[1];
1301                var domainSlashPos = domain.indexOf('/');
1302                if(domainSlashPos > -1)
1303                    domain = domain.substr(0, domainSlashPos);
1304
1305                return $.formUtils.validators.validate_domain.validatorFunction(domain); // todo: add support for IP-addresses
1306            }
1307            return false;
1308        },
1309        errorMessage : '',
1310        errorMessageKey: 'badUrl'
1311    });
1312
1313    /*
1314    * Validate number (floating or integer)
1315    */
1316    $.formUtils.addValidator({
1317        name : 'number',
1318        validatorFunction : function(val, $el, config) {
1319            if(val !== '') {
1320                var allowing = $el.valAttr('allowing') || '',
1321                    decimalSeparator = $el.valAttr('decimal-separator') || config.decimalSeparator,
1322                    allowsRange = false,
1323                    begin, end;
1324
1325                if(allowing.indexOf('number') == -1)
1326                    allowing += ',number';
1327
1328                if(allowing.indexOf('negative') > -1 && val.indexOf('-') === 0) {
1329                    val = val.substr(1);
1330                }
1331
1332                if (allowing.indexOf('range') > -1)
1333                {
1334                    begin = parseFloat(allowing.substring(allowing.indexOf("[")+1, allowing.indexOf(";")));
1335                    end = parseFloat(allowing.substring(allowing.indexOf(";")+1,allowing.indexOf("]")));
1336                    allowsRange = true;
1337                }
1338
1339                if(allowing.indexOf('number') > -1 && val.replace(/[0-9]/g, '') === '' && (!allowsRange || (val >= begin && val <= end)) ) {
1340                    return true;
1341                }
1342                if(allowing.indexOf('float') >
1342 -1 && val.match(new RegExp('^([0-9]+)\\'+decimalSeparator+'([0-9]+)$')) !== null && (!allowsRange || (val >= begin && val <= end)) ) {
1343                    return true;
1344                }
1345            }
1346            return false;
1347        },
1348        errorMessage : '',
1349        errorMessageKey: 'badInt'
1350    });
1351
1352    /*
1353     * Validate alpha numeric
1354     */
1355    $.formUtils.addValidator({
1356        name : 'alphanumeric',
1357        validatorFunction : function(val, $el, config, language) {
1358            var patternStart = '^([a-zA-Z0-9',
1359                patternEnd = ']+)$',
1360                additionalChars = $el.attr('data-validation-allowing'),
1361                pattern = '';
1362
1363            if( additionalChars ) {
1364                pattern = patternStart + additionalChars + patternEnd;
1365                var extra = additionalChars.replace(/\\/g, '');
1366                if( extra.indexOf(' ') > -1 ) {
1367                    extra = extra.replace(' ', '');
1368                    extra += ' and spaces ';
1369                }
1370                this.errorMessage = language.badAlphaNumeric + language.badAlphaNumericExtra + extra;
1371            } else {
1372                pattern = patternStart + patternEnd;
1373                this.errorMessage = language.badAlphaNumeric;
1374            }
1375
1376            return new RegExp(pattern).test(val);
1377        },
1378        errorMessage : '',
1379        errorMessageKey: ''
1380    });
1381
1382    /*
1383    * Validate against regexp
1384    */
1385    $.formUtils.addValidator({
1386        name : 'custom',
1387        validatorFunction : function(val, $el, config) {
1388            var regexp = new RegExp($el.valAttr('regexp'));
1389            return regexp.test(val);
1390        },
1391        errorMessage : '',
1392        errorMessageKey: 'badCustomVal'
1393    });
1394
1395    /*
1396    * Validate date
1397    */
1398    $.formUtils.addValidator({
1399        name : 'date',
1400        validatorFunction : function(date, $el, conf) {
1401            var dateFormat = 'yyyy-mm-dd';
1402            if($el.valAttr('format')) {
1403                dateFormat = $el.valAttr('format');
1404            }
1405            else if( conf.dateFormat ) {
1406                dateFormat = conf.dateFormat;
1407            }
1408
1409            return $.formUtils.parseDate(date, dateFormat) !== false;
1410        },
1411        errorMessage : '',
1412        errorMessageKey: 'badDate'
1413    });
1414
1415
1416    /*
1417    * Validate group of checkboxes, validate qty required is checked
1418    * written by Steve Wasiura : http://stevewasiura.waztech.com
1419    * element attrs
1420    *    data-validation="checkbox_group"
1421    *    data-validation-qty="1-2"  // min 1 max 2
1422    *    data-validation-error-msg="chose min 1, max of 2 checkboxes"
1423    */
1424    $.formUtils.addValidator({
1425        name : 'checkbox_group',
1426        validatorFunction : function(val, $el, config, lang, $form) 
1427        {   // preset return var
1428            var checkResult = true;
1429            // get name of element. since it is a checkbox group, all checkboxes will have same name
1430            var elname = $el.attr('name');
1431            // get count of checked checkboxes with this name
1432            var checkedCount = $("input[type=checkbox][name^='"+elname+"']:checked", $form).length;
1433            // get el attr that specs qty required / allowed
1434            var qtyAllowed = $el.valAttr('qty');
1435            if (qtyAllowed == undefined) {
1436                var elementType = $el.get(0).nodeName;
1437                alert('Attribute "data-validation-qty" is missing from '+elementType+' named '+$el.attr('name'));
1438            }
1439            // call Utility function to check if count is above min, below max, within range etc.
1440            var qtyCheckResults = $.formUtils.numericRangeCheck(checkedCount, qtyAllowed) ;
1441            // results will be array, [0]=result str, [1]=qty int
1442            switch(qtyCheckResults[0] ) {   
1443                // outside allowed range
1444                case "out":
1445                    this.errorMessage = lang.groupCheckedRangeStart + qtyAllowed + lang.groupCheckedEnd;
1446                    checkResult = false;
1447                    break;
1448                // below min qty
1449                case "min":
1450                    this.errorMessage = lang.groupCheckedTooFewStart + qtyCheckResults[1] + lang.groupCheckedEnd;
1451                    checkResult = false;
1452                    break;
1453                // above max qty
1454                case "max":
1455                    this.errorMessage = lang.groupCheckedTooManyStart + qtyCheckResults[1] + lang.groupCheckedEnd;
1456                    checkResult = false;
1457                    break;
1458                // ok
1459                default:
1460                    checkResult = true;
1461            }
1462            
1463        return checkResult;
1464        
1465        } // remove comma
1466     //   errorMessage : '', // set above in switch statement
1467     //   errorMessageKey: '' // not used
1468    });
1469
1470})(jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.