zoukankan      html  css  js  c++  java
  • 使用jquery.validation+jquery.poshytip做表单验证--未完待续

    jqueryValidate的具体使用方法很多,这里就不在赘述,这一次只谈一下怎样简单的实现表单验证。

    整片文章目的,通过JQvalidation按表单属性配置规则验证,并将验证结果通过poshytip来更灵活的展示在不同的位置。

    这里有个问题通过表单标签定义规则不能实现高级复杂的验证,所以复杂的验证还是要通过js去单个实现。

    效果如下:

    第一步:为表单配置验证属性,实现验证规则的随心所欲。

    目的:通过js的引用配置,可以实现对自己定义的标签属性指定json格式的验证规则。

    页面引入js

     <script src="~/js/jquery-migrate-1.8.1.min.js"></script>
     <script src="~/Scripts/jquery.validate.js"></script>
     <script src="~/Scripts/jquery.metadata.js"></script>//封装了对validate更简洁的调用

    简单验证方式

     <input id="ssp" class="required" />

    引用metadata之后就可以所有直接标签定义验证内容和错误消息

    重点:这里注意 required 前后的空格,可以查看metadata中的规则,空格是一种书写格式必须保留。

      <input class="{validate:{ required :true},messages:{ required:'请输入名称' }}" id="ssp" />

    但这样会影响到表单正常的样式设置,所以可以通过metadata来修改。这样就支持在data-validation属性中使用json格式定义

    1.修改metadata中属性初始化

     metadata : {
        defaults : {
            type: 'attr',
          name: 'data-validation',//修改原属性class为data-validation
          cre: /({.*})/,
          single: 'metadata'
        },
        setType: function( type, name ){
          this.defaults.type = type;
          this.defaults.name = name;
        }
    }

    2.修改了这个后发现还是不能用,最后还要修改validation中的class定义

    classRules: function(element) {
            var rules = {};
            var classes = $(element).attr('data-validation');//修改class为要定义的属性
            if ( classes ) {
                $.each(classes.split(' '), function() {
                    if (this in $.validator.classRuleSettings) {
                        $.extend(rules, $.validator.classRuleSettings[this]);
                    }
                });
            }
            return rules;
        },

    ==有些文档中讲到在文件尾部通过setType来设置,就可以实现,单在我这里并没有得到成功的结果。

    $.metadata.setType("attr", "data-validation");

    到这里 第一步完成可以实现在表单中通过扩展属性来书写验证规则

     第二步:添加poshytip引用实现页面气泡提示效果

    目的:完成对验证结果错误消息的接受并进行消息气泡形式显示

    这里只添加引用还不行,需要对原版的JS和一些触发事件做一些修改才能完美实现我们想要的功能

    <script src="~/plugs/poshytip/jquery.poshytip.js"></script>
    <link href="~/plugs/poshytip/tip-twitter/tip-twitter.css" rel="stylesheet" />//样式也是必须要引入的,可以根据配置的皮肤选择引用样式文件

    这里引用的jquery.poshytip.js是Poshy Tip jQuery plugin v1.0,针对JQuery1.9以上版本还存在一定的兼容问题出现以下异常

    *$.browser这个api从jQuery1.9开始就正式废除,处理错误:Cannot read property ‘msie’ of undefined*/

    为了兼容不同的jQuery版本我们需要在文件头加上这样一行代码就可以放心在各个jQuery版本下运行了

    jQuery.browser = {}; (function () { jQuery.browser.msie = false; jQuery.browser.version = 0; if (navigator.userAgent.match(/MSIE ([0-9]+)./)) { jQuery.browser.msie = true; jQuery.browser.version = RegExp.$1; } })();

    当然引入的样式文件要和配置文件里所配置的皮肤一致

    poshytip配置:

    $.fn.poshytip.defaults = {
            content:         '[title]',    // content to display ('[title]', 'string', element, function(updateCallback){...}, jQuery)
            className:        'tip-twitter',    // class for the tips
            bgImageFrameSize:    10,        // 提示框背景图片的大小
            showTimeout:        500,        // timeout before showing the tip (in milliseconds 1000 == 1 second)
            hideTimeout:        100,        // timeout before hiding the tip
            showOn:            'hover',    // 触发何种事件显示提示框 ('hover', 'focus', 'none') - use 'none' to trigger it manually
            alignTo: 'target',    // 设置箭头位置('cursor', 'target')
            alignX: 'right',    // 水平对齐相对于鼠标光标或目标元素
                                // ('right', 'center', 'left', 'inner-left', 'inner-right') - 'inner-*' matter if alignTo:'target'
            alignY: 'center',        // 垂直对齐相对于鼠标光标或目标元素
                                // ('bottom', 'center', 'top', 'inner-bottom', 'inner-top') - 'inner-*' matter if alignTo:'target'
            offsetX:        12,        // 设置提示框横向偏移 - doesn't matter if alignX:'center'
            offsetY:        18,        //     设置提示框纵向偏移 - doesn't matter if alignY:'center'
            allowTipHover:        true,        // 当鼠标悬在tip上时,不隐藏tip- matters only if showOn:'hover'
            followCursor:        false,        //提示跟随光标移动- matters only if showOn:'hover' and alignTo:'cursor'
            fade:             true,        // 使用fade动画
            slide:             true,        // 使用slide动画
            slideOffset:         8,        // slide动画相抵消
            showAniDuration:     300,        // 显示动画时长 - set to 0 if you don't want show animation
            hideAniDuration: 300,        //  隐藏动画的持续时间 - set to 0 if you don't want hide animation
            refreshAniDuration:1000   //异步更新提示时,动画的持续时间
        };

    从上边这些配置参数中,我们看到showOn属性有三个值的形式('hover', 'focus', 'none') ,但是这三个值对于原生poshytip来讲挺合理也够用了,但是我们现在需要在发生异常时显示tip的

    错误提示,因此需要单独添加一个触发事件就可以解决问题。

    这里我们加一个‘elemshow’用于在每个元素验证错误时弹出错误提示,最后一个触发事件为此次需要特别添加,并且制定提示框在3秒后自动消失。

    switch (this.opts.showOn) {
                    case 'hover':
                        this.$elm.bind({
                            'mouseenter.poshytip': $.proxy(this.mouseenter, this),
                            'mouseleave.poshytip': $.proxy(this.mouseleave, this)
                        });
                        if (this.opts.alignTo == 'cursor')
                            this.$elm.bind('mousemove.poshytip', $.proxy(this.mousemove, this));
                        if (this.opts.allowTipHover)
                            this.$tip.hover($.proxy(this.clearTimeouts, this), $.proxy(this.hide, this));
                        break;
                    case 'focus':
                        this.$elm.bind({
                            'focus.poshytip': $.proxy(this.show, this),
                            'blur.poshytip': $.proxy(this.hide, this)
                        });
                        break;
                        //为指定元素tip提醒——刘自洋20160420
                    case 'elemshow':
                        this.showposition();
                        this.$tip.hover($.proxy(this.clearTimeouts, this), $.proxy(this.hide, this));
                        setTimeout($.proxy(this.hide, this),3000);
                        break;
                }

    到这里对poshytip和validate的改造就完成了,接下来我们考虑一下对于表单的验证,当鼠标离开文本框时或者键盘输入时都会用到验证,这里我们就要对validate的onfocusin,onfocusout

    事件进行重写。

     onfocusin: function (element) {
            this.lastActive = element;
            this.addWrapper(this.errorsFor(element)).hide();
            var focusintip = $(element).attr('focusin');
            if (focusintip && $(element).parent().children(".focusin").length === 0) {
                //$(element).parent().poshytip({ content: focusintip, showOn: 'elemshow' });//tip光标选中后提示
            }
          
            // Hide error label and remove error class on focus if enabled
            if (this.settings.focusCleanup) {
                if (this.settings.unhighlight) {
                    this.settings.unhighlight.call(this, element, this.settings.errorClass, this.settings.validClass);
                }
                this.hideThese(this.errorsFor(element));
            }
        }

    这里主要实现在用户选择输入框时给出填写提示,但是这东西很多时候也没啥必要,这里就注释掉了。

     /*焦点离开*/
        onfocusout: function (element) {
        $(element).valid();
    if ( !this.checkable( element ) && ( element.name in this.submitted || !this.optional( element ) ) ) { this.element( element ); } }

    焦点离开时,给出验证并提示验证结果。

    设置完触发事件并不能算完事,最重要的是要为错误提示指定显示位置和显示方式进行修改。

      errorPlacement: function (error, element) {//错误信息显示位置
            if ($(error).text() != "") {
                $(element).poshytip({ content: $(error).text(), showOn: 'elemshow' });
                //$(element).parent().poshytip({ content: $(error).text(), showOn: 'elemshow' });//父容器追加提示
            }
        }

    通过前面定义的elemshow这里就派上用场了,错误信息展示。可以根据需要显示在当前目标元素为参照或者设置其父节点为参照。

    至于显示位置不要忘了是通过poshytip的默认配置去调整。

     第三步:开始验证

    目的:为所有只要添加了data-validation标记并添加验证规则的表单做规则验证

    为所有form表单添加验证,实现这一功能我们只需在全局js中添加以下代码就可实现。

    /*指定表单加上验证才能提交*/
    $(function () {
        $("form").validate();
    });

    第四步:代码整理

    上边说了这么多其实就是对这些插件的一些修改,看明白了意思就行。下面把各插件完整js奉上,可以直接放入项目应用。

    --jquery.validate.js

    --修改class属性为自己所定义的验证属性data-validation

    /*!
     * jQuery Validation Plugin 1.11.1
     *
     * http://bassistance.de/jquery-plugins/jquery-plugin-validation/
     * http://docs.jquery.com/Plugins/Validation
     *
     * Copyright 2013 Jörn Zaefferer
     * Released under the MIT license:
     *   http://www.opensource.org/licenses/mit-license.php
     */
    
    (function ($) {
    
        $.extend($.fn, {
            // http://docs.jquery.com/Plugins/Validation/validate
            validate: function (options) {
    
                // if nothing is selected, return nothing; can't chain anyway
                if (!this.length) {
                    if (options && options.debug && window.console) {
                        console.warn("Nothing selected, can't validate, returning nothing.");
                    }
                    return;
                }
    
                // check if a validator for this form was already created
                var validator = $.data(this[0], "validator");
                if (validator) {
                    return validator;
                }
    
                // Add novalidate tag if HTML5.
                this.attr("novalidate", "novalidate");
    
                validator = new $.validator(options, this[0]);
                $.data(this[0], "validator", validator);
    
                if (validator.settings.onsubmit) {
    
                    this.validateDelegate(":submit", "click", function (event) {
                        if (validator.settings.submitHandler) {
                            validator.submitButton = event.target;
                        }
                        // allow suppressing validation by adding a cancel class to the submit button
                        if ($(event.target).hasClass("cancel")) {
                            validator.cancelSubmit = true;
                        }
    
                        // allow suppressing validation by adding the html5 formnovalidate attribute to the submit button
                        if ($(event.target).attr("formnovalidate") !== undefined) {
                            validator.cancelSubmit = true;
                        }
                    });
    
                    // validate the form on submit
                    this.submit(function (event) {
                        if (validator.settings.debug) {
                            // prevent form submit to be able to see console output
                            event.preventDefault();
                        }
                        function handle() {
                            var hidden;
                            if (validator.settings.submitHandler) {
                                if (validator.submitButton) {
                                    // insert a hidden input as a replacement for the missing submit button
                                    hidden = $("<input type='hidden'/>").attr("name", validator.submitButton.name).val($(validator.submitButton).val()).appendTo(validator.currentForm);
                                }
                                validator.settings.submitHandler.call(validator, validator.currentForm, event);
                                if (validator.submitButton) {
                                    // and clean up afterwards; thanks to no-block-scope, hidden can be referenced
                                    hidden.remove();
                                }
                                return false;
                            }
                            return true;
                        }
    
                        // prevent submit for invalid forms or custom submit handlers
                        if (validator.cancelSubmit) {
                            validator.cancelSubmit = false;
                            return handle();
                        }
                        if (validator.form()) {
                            if (validator.pendingRequest) {
                                validator.formSubmitted = true;
                                return false;
                            }
                            return handle();
                        } else {
                            validator.focusInvalid();
                            return false;
                        }
                    });
                }
    
                return validator;
            },
            // http://docs.jquery.com/Plugins/Validation/valid
            valid: function () {
                if ($(this[0]).is("form")) {
                    return this.validate().form();
                } else {
                    var valid = true;
                    var validator = $(this[0].form).validate();
                    this.each(function () {
                        valid = valid && validator.element(this);
                    });
                    return valid;
                }
            },
            // attributes: space seperated list of attributes to retrieve and remove
            removeAttrs: function (attributes) {
                var result = {},
                    $element = this;
                $.each(attributes.split(/s/), function (index, value) {
                    result[value] = $element.attr(value);
                    $element.removeAttr(value);
                });
                return result;
            },
            // http://docs.jquery.com/Plugins/Validation/rules
            rules: function (command, argument) {
                var element = this[0];
    
                if (command) {
                    var settings = $.data(element.form, "validator").settings;
                    var staticRules = settings.rules;
                    var existingRules = $.validator.staticRules(element);
                    switch (command) {
                        case "add":
                            $.extend(existingRules, $.validator.normalizeRule(argument));
                            // remove messages from rules, but allow them to be set separetely
                            delete existingRules.messages;
                            staticRules[element.name] = existingRules;
                            if (argument.messages) {
                                settings.messages[element.name] = $.extend(settings.messages[element.name], argument.messages);
                            }
                            break;
                        case "remove":
                            if (!argument) {
                                delete staticRules[element.name];
                                return existingRules;
                            }
                            var filtered = {};
                            $.each(argument.split(/s/), function (index, method) {
                                filtered[method] = existingRules[method];
                                delete existingRules[method];
                            });
                            return filtered;
                    }
                }
    
                var data = $.validator.normalizeRules(
                $.extend(
                    {},
                    $.validator.classRules(element),
                    $.validator.attributeRules(element),
                    $.validator.dataRules(element),
                    $.validator.staticRules(element)
                ), element);
    
                // make sure required is at front
                if (data.required) {
                    var param = data.required;
                    delete data.required;
                    data = $.extend({ required: param }, data);
                }
    
                return data;
            }
        });
    
        // Custom selectors
        $.extend($.expr[":"], {
            // http://docs.jquery.com/Plugins/Validation/blank
            blank: function (a) { return !$.trim("" + $(a).val()); },
            // http://docs.jquery.com/Plugins/Validation/filled
            filled: function (a) { return !!$.trim("" + $(a).val()); },
            // http://docs.jquery.com/Plugins/Validation/unchecked
            unchecked: function (a) { return !$(a).prop("checked"); }
        });
    
        // constructor for validator
        $.validator = function (options, form) {
            this.settings = $.extend(true, {}, $.validator.defaults, options);
            this.currentForm = form;
            this.init();
        };
    
        $.validator.format = function (source, params) {
            if (arguments.length === 1) {
                return function () {
                    var args = $.makeArray(arguments);
                    args.unshift(source);
                    return $.validator.format.apply(this, args);
                };
            }
            if (arguments.length > 2 && params.constructor !== Array) {
                params = $.makeArray(arguments).slice(1);
            }
            if (params.constructor !== Array) {
                params = [params];
            }
            $.each(params, function (i, n) {
                source = source.replace(new RegExp("\{" + i + "\}", "g"), function () {
                    return n;
                });
            });
            return source;
        };
    
        $.extend($.validator, {
    
            defaults: {
                messages: {},
                groups: {},
                rules: {},
                errorClass: "error",
                validClass: "valid",
                errorElement: "label",
                focusInvalid: true,
                errorContainer: $([]),
                errorLabelContainer: $([]),
                onsubmit: true,
                ignore: ":hidden",
                ignoreTitle: false,
                onfocusin: function (element, event) {
                    this.lastActive = element;
    
                    // hide error label and remove error class on focus if enabled
                    if (this.settings.focusCleanup && !this.blockFocusCleanup) {
                        if (this.settings.unhighlight) {
                            this.settings.unhighlight.call(this, element, this.settings.errorClass, this.settings.validClass);
                        }
                        this.addWrapper(this.errorsFor(element)).hide();
                    }
                },
                onfocusout: function (element, event) {
                    if (!this.checkable(element) && (element.name in this.submitted || !this.optional(element))) {
                        this.element(element);
                    }
                },
                onkeyup: function (element, event) {
                    if (event.which === 9 && this.elementValue(element) === "") {
                        return;
                    } else if (element.name in this.submitted || element === this.lastElement) {
                        this.element(element);
                    }
                },
                onclick: function (element, event) {
                    // click on selects, radiobuttons and checkboxes
                    if (element.name in this.submitted) {
                        this.element(element);
                    }
                        // or option elements, check parent select in that case
                    else if (element.parentNode.name in this.submitted) {
                        this.element(element.parentNode);
                    }
                },
                highlight: function (element, errorClass, validClass) {
                    if (element.type === "radio") {
                        this.findByName(element.name).addClass(errorClass).removeClass(validClass);
                    } else {
                        $(element).addClass(errorClass).removeClass(validClass);
                    }
                },
                unhighlight: function (element, errorClass, validClass) {
                    if (element.type === "radio") {
                        this.findByName(element.name).removeClass(errorClass).addClass(validClass);
                    } else {
                        $(element).removeClass(errorClass).addClass(validClass);
                    }
                }
            },
    
            // http://docs.jquery.com/Plugins/Validation/Validator/setDefaults
            setDefaults: function (settings) {
                $.extend($.validator.defaults, settings);
            },
    
            messages: {
                required: "This field is required.",
                remote: "Please fix this field.",
                email: "Please enter a valid email address.",
                url: "Please enter a valid URL.",
                date: "Please enter a valid date.",
                dateISO: "Please enter a valid date (ISO).",
                number: "Please enter a valid number.",
                digits: "Please enter only digits.",
                creditcard: "Please enter a valid credit card number.",
                equalTo: "Please enter the same value again.",
                maxlength: $.validator.format("Please enter no more than {0} characters."),
                minlength: $.validator.format("Please enter at least {0} characters."),
                rangelength: $.validator.format("Please enter a value between {0} and {1} characters long."),
                range: $.validator.format("Please enter a value between {0} and {1}."),
                max: $.validator.format("Please enter a value less than or equal to {0}."),
                min: $.validator.format("Please enter a value greater than or equal to {0}.")
            },
    
            autoCreateRanges: false,
    
            prototype: {
    
                init: function () {
                    this.labelContainer = $(this.settings.errorLabelContainer);
                    this.errorContext = this.labelContainer.length && this.labelContainer || $(this.currentForm);
                    this.containers = $(this.settings.errorContainer).add(this.settings.errorLabelContainer);
                    this.submitted = {};
                    this.valueCache = {};
                    this.pendingRequest = 0;
                    this.pending = {};
                    this.invalid = {};
                    this.reset();
    
                    var groups = (this.groups = {});
                    $.each(this.settings.groups, function (key, value) {
                        if (typeof value === "string") {
                            value = value.split(/s/);
                        }
                        $.each(value, function (index, name) {
                            groups[name] = key;
                        });
                    });
                    var rules = this.settings.rules;
                    $.each(rules, function (key, value) {
                        rules[key] = $.validator.normalizeRule(value);
                    });
    
                    function delegate(event) {
                        var validator = $.data(this[0].form, "validator"),
                            eventType = "on" + event.type.replace(/^validate/, "");
                        if (validator.settings[eventType]) {
                            validator.settings[eventType].call(validator, this[0], event);
                        }
                    }
                    $(this.currentForm)
                        .validateDelegate(":text, [type='password'], [type='file'], select, textarea, " +
                            "[type='number'], [type='search'] ,[type='tel'], [type='url'], " +
                            "[type='email'], [type='datetime'], [type='date'], [type='month'], " +
                            "[type='week'], [type='time'], [type='datetime-local'], " +
                            "[type='range'], [type='color'] ",
                            "focusin focusout keyup", delegate)
                        .validateDelegate("[type='radio'], [type='checkbox'], select, option", "click", delegate);
    
                    if (this.settings.invalidHandler) {
                        $(this.currentForm).bind("invalid-form.validate", this.settings.invalidHandler);
                    }
                },
    
                // http://docs.jquery.com/Plugins/Validation/Validator/form
                form: function () {
                    this.checkForm();
                    $.extend(this.submitted, this.errorMap);
                    this.invalid = $.extend({}, this.errorMap);
                    if (!this.valid()) {
                        $(this.currentForm).triggerHandler("invalid-form", [this]);
                    }
                    this.showErrors();
                    return this.valid();
                },
    
                checkForm: function () {
                    this.prepareForm();
                    for (var i = 0, elements = (this.currentElements = this.elements()) ; elements[i]; i++) {
                        this.check(elements[i]);
                    }
                    return this.valid();
                },
    
                // http://docs.jquery.com/Plugins/Validation/Validator/element
                element: function (element) {
                    element = this.validationTargetFor(this.clean(element));
                    this.lastElement = element;
                    this.prepareElement(element);
                    this.currentElements = $(element);
                    var result = this.check(element) !== false;
                    if (result) {
                        delete this.invalid[element.name];
                    } else {
                        this.invalid[element.name] = true;
                    }
                    if (!this.numberOfInvalids()) {
                        // Hide error containers on last error
                        this.toHide = this.toHide.add(this.containers);
                    }
                    this.showErrors();
                    return result;
                },
    
                // http://docs.jquery.com/Plugins/Validation/Validator/showErrors
                showErrors: function (errors) {
                    if (errors) {
                        // add items to error list and map
                        $.extend(this.errorMap, errors);
                        this.errorList = [];
                        for (var name in errors) {
                            this.errorList.push({
                                message: errors[name],
                                element: this.findByName(name)[0]
                            });
                        }
                        // remove items from success list
                        this.successList = $.grep(this.successList, function (element) {
                            return !(element.name in errors);
                        });
                    }
                    if (this.settings.showErrors) {
                        this.settings.showErrors.call(this, this.errorMap, this.errorList);
                    } else {
                        this.defaultShowErrors();
                    }
                },
    
                // http://docs.jquery.com/Plugins/Validation/Validator/resetForm
                resetForm: function () {
                    if ($.fn.resetForm) {
                        $(this.currentForm).resetForm();
                    }
                    this.submitted = {};
                    this.lastElement = null;
                    this.prepareForm();
                    this.hideErrors();
                    this.elements().removeClass(this.settings.errorClass).removeData("previousValue");
                },
    
                numberOfInvalids: function () {
                    return this.objectLength(this.invalid);
                },
    
                objectLength: function (obj) {
                    var count = 0;
                    for (var i in obj) {
                        count++;
                    }
                    return count;
                },
    
                hideErrors: function () {
                    this.addWrapper(this.toHide).hide();
                },
    
                valid: function () {
                    return this.size() === 0;
                },
    
                size: function () {
                    return this.errorList.length;
                },
    
                focusInvalid: function () {
                    if (this.settings.focusInvalid) {
                        try {
                            $(this.findLastActive() || this.errorList.length && this.errorList[0].element || [])
                            .filter(":visible")
                            .focus()
                            // manually trigger focusin event; without it, focusin handler isn't called, findLastActive won't have anything to find
                            .trigger("focusin");
                        } catch (e) {
                            // ignore IE throwing errors when focusing hidden elements
                        }
                    }
                },
    
                findLastActive: function () {
                    var lastActive = this.lastActive;
                    return lastActive && $.grep(this.errorList, function (n) {
                        return n.element.name === lastActive.name;
                    }).length === 1 && lastActive;
                },
    
                elements: function () {
                    var validator = this,
                        rulesCache = {};
    
                    // select all valid inputs inside the form (no submit or reset buttons)
                    return $(this.currentForm)
                    .find("input, select, textarea")
                    .not(":submit, :reset, :image, [disabled]")
                    .not(this.settings.ignore)
                    .filter(function () {
                        if (!this.name && validator.settings.debug && window.console) {
                            console.error("%o has no name assigned", this);
                        }
    
                        // select only the first element for each name, and only those with rules specified
                        if (this.name in rulesCache || !validator.objectLength($(this).rules())) {
                            return false;
                        }
    
                        rulesCache[this.name] = true;
                        return true;
                    });
                },
    
                clean: function (selector) {
                    return $(selector)[0];
                },
    
                errors: function () {
                    var errorClass = this.settings.errorClass.replace(" ", ".");
                    return $(this.settings.errorElement + "." + errorClass, this.errorContext);
                },
    
                reset: function () {
                    this.successList = [];
                    this.errorList = [];
                    this.errorMap = {};
                    this.toShow = $([]);
                    this.toHide = $([]);
                    this.currentElements = $([]);
                },
    
                prepareForm: function () {
                    this.reset();
                    this.toHide = this.errors().add(this.containers);
                },
    
                prepareElement: function (element) {
                    this.reset();
                    this.toHide = this.errorsFor(element);
                },
    
                elementValue: function (element) {
                    var type = $(element).attr("type"),
                        val = $(element).val();
    
                    if (type === "radio" || type === "checkbox") {
                        return $("input[name='" + $(element).attr("name") + "']:checked").val();
                    }
    
                    if (typeof val === "string") {
                        return val.replace(/
    /g, "");
                    }
                    return val;
                },
    
                check: function (element) {
                    element = this.validationTargetFor(this.clean(element));
    
                    var rules = $(element).rules();
                    var dependencyMismatch = false;
                    var val = this.elementValue(element);
                    var result;
    
                    for (var method in rules) {
                        var rule = { method: method, parameters: rules[method] };
                        try {
    
                            result = $.validator.methods[method].call(this, val, element, rule.parameters);
    
                            // if a method indicates that the field is optional and therefore valid,
                            // don't mark it as valid when there are no other rules
                            if (result === "dependency-mismatch") {
                                dependencyMismatch = true;
                                continue;
                            }
                            dependencyMismatch = false;
    
                            if (result === "pending") {
                                this.toHide = this.toHide.not(this.errorsFor(element));
                                return;
                            }
    
                            if (!result) {
                                this.formatAndAdd(element, rule);
                                return false;
                            }
                        } catch (e) {
                            if (this.settings.debug && window.console) {
                                console.log("Exception occurred when checking element " + element.id + ", check the '" + rule.method + "' method.", e);
                            }
                            throw e;
                        }
                    }
                    if (dependencyMismatch) {
                        return;
                    }
                    if (this.objectLength(rules)) {
                        this.successList.push(element);
                    }
                    return true;
                },
    
                // return the custom message for the given element and validation method
                // specified in the element's HTML5 data attribute
                customDataMessage: function (element, method) {
                    return $(element).data("msg-" + method.toLowerCase()) || (element.attributes && $(element).attr("data-msg-" + method.toLowerCase()));
                },
    
                // return the custom message for the given element name and validation method
                customMessage: function (name, method) {
                    var m = this.settings.messages[name];
                    return m && (m.constructor === String ? m : m[method]);
                },
    
                // return the first defined argument, allowing empty strings
                findDefined: function () {
                    for (var i = 0; i < arguments.length; i++) {
                        if (arguments[i] !== undefined) {
                            return arguments[i];
                        }
                    }
                    return undefined;
                },
    
                defaultMessage: function (element, method) {
                    return this.findDefined(
                        this.customMessage(element.name, method),
                        this.customDataMessage(element, method),
                        // title is never undefined, so handle empty string as undefined
                        !this.settings.ignoreTitle && element.title || undefined,
                        $.validator.messages[method],
                        "<strong>Warning: No message defined for " + element.name + "</strong>"
                    );
                },
    
                formatAndAdd: function (element, rule) {
                    var message = this.defaultMessage(element, rule.method),
                        theregex = /$?{(d+)}/g;
                    if (typeof message === "function") {
                        message = message.call(this, rule.parameters, element);
                    } else if (theregex.test(message)) {
                        message = $.validator.format(message.replace(theregex, "{$1}"), rule.parameters);
                    }
                    this.errorList.push({
                        message: message,
                        element: element
                    });
    
                    this.errorMap[element.name] = message;
                    this.submitted[element.name] = message;
                },
    
                addWrapper: function (toToggle) {
                    if (this.settings.wrapper) {
                        toToggle = toToggle.add(toToggle.parent(this.settings.wrapper));
                    }
                    return toToggle;
                },
    
                defaultShowErrors: function () {
                    var i, elements;
                    for (i = 0; this.errorList[i]; i++) {
                        var error = this.errorList[i];
                        if (this.settings.highlight) {
                            this.settings.highlight.call(this, error.element, this.settings.errorClass, this.settings.validClass);
                        }
                        this.showLabel(error.element, error.message);
                    }
                    if (this.errorList.length) {
                        this.toShow = this.toShow.add(this.containers);
                    }
                    if (this.settings.success) {
                        for (i = 0; this.successList[i]; i++) {
                            this.showLabel(this.successList[i]);
                        }
                    }
                    if (this.settings.unhighlight) {
                        for (i = 0, elements = this.validElements() ; elements[i]; i++) {
                            this.settings.unhighlight.call(this, elements[i], this.settings.errorClass, this.settings.validClass);
                        }
                    }
                    this.toHide = this.toHide.not(this.toShow);
                    this.hideErrors();
                    this.addWrapper(this.toShow).show();
                },
    
                validElements: function () {
                    return this.currentElements.not(this.invalidElements());
                },
    
                invalidElements: function () {
                    return $(this.errorList).map(function () {
                        return this.element;
                    });
                },
    
                showLabel: function (element, message) {
                    var label = this.errorsFor(element);
                    if (label.length) {
                        // refresh error/success class
                        label.removeClass(this.settings.validClass).addClass(this.settings.errorClass);
                        // replace message on existing label
                        label.html(message);
                    } else {
                        // create label
                        label = $("<" + this.settings.errorElement + ">")
                            .attr("for", this.idOrName(element))
                            .addClass(this.settings.errorClass)
                            .html(message || "");
                        if (this.settings.wrapper) {
                            // make sure the element is visible, even in IE
                            // actually showing the wrapped element is handled elsewhere
                            label = label.hide().show().wrap("<" + this.settings.wrapper + "/>").parent();
                        }
                        if (!this.labelContainer.append(label).length) {
                            if (this.settings.errorPlacement) {
                                this.settings.errorPlacement(label, $(element));
                            } else {
                                label.insertAfter(element);
                            }
                        }
                    }
                    if (!message && this.settings.success) {
                        label.text("");
                        if (typeof this.settings.success === "string") {
                            label.addClass(this.settings.success);
                        } else {
                            this.settings.success(label, element);
                        }
                    }
                    this.toShow = this.toShow.add(label);
                },
    
                errorsFor: function (element) {
                    var name = this.idOrName(element);
                    return this.errors().filter(function () {
                        return $(this).attr("for") === name;
                    });
                },
    
                idOrName: function (element) {
                    return this.groups[element.name] || (this.checkable(element) ? element.name : element.id || element.name);
                },
    
                validationTargetFor: function (element) {
                    // if radio/checkbox, validate first element in group instead
                    if (this.checkable(element)) {
                        element = this.findByName(element.name).not(this.settings.ignore)[0];
                    }
                    return element;
                },
    
                checkable: function (element) {
                    return (/radio|checkbox/i).test(element.type);
                },
    
                findByName: function (name) {
                    return $(this.currentForm).find("[name='" + name + "']");
                },
    
                getLength: function (value, element) {
                    switch (element.nodeName.toLowerCase()) {
                        case "select":
                            return $("option:selected", element).length;
                        case "input":
                            if (this.checkable(element)) {
                                return this.findByName(element.name).filter(":checked").length;
                            }
                    }
                    return value.length;
                },
    
                depend: function (param, element) {
                    return this.dependTypes[typeof param] ? this.dependTypes[typeof param](param, element) : true;
                },
    
                dependTypes: {
                    "boolean": function (param, element) {
                        return param;
                    },
                    "string": function (param, element) {
                        return !!$(param, element.form).length;
                    },
                    "function": function (param, element) {
                        return param(element);
                    }
                },
    
                optional: function (element) {
                    var val = this.elementValue(element);
                    return !$.validator.methods.required.call(this, val, element) && "dependency-mismatch";
                },
    
                startRequest: function (element) {
                    if (!this.pending[element.name]) {
                        this.pendingRequest++;
                        this.pending[element.name] = true;
                    }
                },
    
                stopRequest: function (element, valid) {
                    this.pendingRequest--;
                    // sometimes synchronization fails, make sure pendingRequest is never < 0
                    if (this.pendingRequest < 0) {
                        this.pendingRequest = 0;
                    }
                    delete this.pending[element.name];
                    if (valid && this.pendingRequest === 0 && this.formSubmitted && this.form()) {
                        $(this.currentForm).submit();
                        this.formSubmitted = false;
                    } else if (!valid && this.pendingRequest === 0 && this.formSubmitted) {
                        $(this.currentForm).triggerHandler("invalid-form", [this]);
                        this.formSubmitted = false;
                    }
                },
    
                previousValue: function (element) {
                    return $.data(element, "previousValue") || $.data(element, "previousValue", {
                        old: null,
                        valid: true,
                        message: this.defaultMessage(element, "remote")
                    });
                }
    
            },
    
            classRuleSettings: {
                required: { required: true },
                email: { email: true },
                url: { url: true },
                date: { date: true },
                dateISO: { dateISO: true },
                number: { number: true },
                digits: { digits: true },
                creditcard: { creditcard: true }
            },
    
            addClassRules: function (className, rules) {
                if (className.constructor === String) {
                    this.classRuleSettings[className] = rules;
                } else {
                    $.extend(this.classRuleSettings, className);
                }
            },
    
            classRules: function (element) {
                var rules = {};
                var classes = $(element).attr("data-validation");
                if (classes) {
                    $.each(classes.split(" "), function () {
                        if (this in $.validator.classRuleSettings) {
                            $.extend(rules, $.validator.classRuleSettings[this]);
                        }
                    });
                }
                return rules;
            },
    
            attributeRules: function (element) {
                var rules = {};
                var $element = $(element);
                var type = $element[0].getAttribute("type");
    
                for (var method in $.validator.methods) {
                    var value;
    
                    // support for <input required> in both html5 and older browsers
                    if (method === "required") {
                        value = $element.get(0).getAttribute(method);
                        // Some browsers return an empty string for the required attribute
                        // and non-HTML5 browsers might have required="" markup
                        if (value === "") {
                            value = true;
                        }
                        // force non-HTML5 browsers to return bool
                        value = !!value;
                    } else {
                        value = $element.attr(method);
                    }
    
                    // convert the value to a number for number inputs, and for text for backwards compability
                    // allows type="date" and others to be compared as strings
                    if (/min|max/.test(method) && (type === null || /number|range|text/.test(type))) {
                        value = Number(value);
                    }
    
                    if (value) {
                        rules[method] = value;
                    } else if (type === method && type !== 'range') {
                        // exception: the jquery validate 'range' method
                        // does not test for the html5 'range' type
                        rules[method] = true;
                    }
                }
    
                // maxlength may be returned as -1, 2147483647 (IE) and 524288 (safari) for text inputs
                if (rules.maxlength && /-1|2147483647|524288/.test(rules.maxlength)) {
                    delete rules.maxlength;
                }
    
                return rules;
            },
    
            dataRules: function (element) {
                var method, value,
                    rules = {}, $element = $(element);
                for (method in $.validator.methods) {
                    value = $element.data("rule-" + method.toLowerCase());
                    if (value !== undefined) {
                        rules[method] = value;
                    }
                }
                return rules;
            },
    
            staticRules: function (element) {
                var rules = {};
                var validator = $.data(element.form, "validator");
                if (validator.settings.rules) {
                    rules = $.validator.normalizeRule(validator.settings.rules[element.name]) || {};
                }
                return rules;
            },
    
            normalizeRules: function (rules, element) {
                // handle dependency check
                $.each(rules, function (prop, val) {
                    // ignore rule when param is explicitly false, eg. required:false
                    if (val === false) {
                        delete rules[prop];
                        return;
                    }
                    if (val.param || val.depends) {
                        var keepRule = true;
                        switch (typeof val.depends) {
                            case "string":
                                keepRule = !!$(val.depends, element.form).length;
                                break;
                            case "function":
                                keepRule = val.depends.call(element, element);
                                break;
                        }
                        if (keepRule) {
                            rules[prop] = val.param !== undefined ? val.param : true;
                        } else {
                            delete rules[prop];
                        }
                    }
                });
    
                // evaluate parameters
                $.each(rules, function (rule, parameter) {
                    rules[rule] = $.isFunction(parameter) ? parameter(element) : parameter;
                });
    
                // clean number parameters
                $.each(['minlength', 'maxlength'], function () {
                    if (rules[this]) {
                        rules[this] = Number(rules[this]);
                    }
                });
                $.each(['rangelength', 'range'], function () {
                    var parts;
                    if (rules[this]) {
                        if ($.isArray(rules[this])) {
                            rules[this] = [Number(rules[this][0]), Number(rules[this][1])];
                        } else if (typeof rules[this] === "string") {
                            parts = rules[this].split(/[s,]+/);
                            rules[this] = [Number(parts[0]), Number(parts[1])];
                        }
                    }
                });
    
                if ($.validator.autoCreateRanges) {
                    // auto-create ranges
                    if (rules.min && rules.max) {
                        rules.range = [rules.min, rules.max];
                        delete rules.min;
                        delete rules.max;
                    }
                    if (rules.minlength && rules.maxlength) {
                        rules.rangelength = [rules.minlength, rules.maxlength];
                        delete rules.minlength;
                        delete rules.maxlength;
                    }
                }
    
                return rules;
            },
    
            // Converts a simple string to a {string: true} rule, e.g., "required" to {required:true}
            normalizeRule: function (data) {
                if (typeof data === "string") {
                    var transformed = {};
                    $.each(data.split(/s/), function () {
                        transformed[this] = true;
                    });
                    data = transformed;
                }
                return data;
            },
    
            // http://docs.jquery.com/Plugins/Validation/Validator/addMethod
            addMethod: function (name, method, message) {
                $.validator.methods[name] = method;
                $.validator.messages[name] = message !== undefined ? message : $.validator.messages[name];
                if (method.length < 3) {
                    $.validator.addClassRules(name, $.validator.normalizeRule(name));
                }
            },
    
            methods: {
    
                // http://docs.jquery.com/Plugins/Validation/Methods/required
                required: function (value, element, param) {
                    // check if dependency is met
                    if (!this.depend(param, element)) {
                        return "dependency-mismatch";
                    }
                    if (element.nodeName.toLowerCase() === "select") {
                        // could be an array for select-multiple or a string, both are fine this way
                        var val = $(element).val();
                        return val && val.length > 0;
                    }
                    if (this.checkable(element)) {
                        return this.getLength(value, element) > 0;
                    }
                    return $.trim(value).length > 0;
                },
    
                // http://docs.jquery.com/Plugins/Validation/Methods/email
                email: function (value, element) {
                    // contributed by Scott Gonzalez: http://projects.scottsplayground.com/email_address_validation/
                    return this.optional(element) || /^((([a-z]|d|[!#$%&'*+-/=?^_`{|}~]|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF])+(.([a-z]|d|[!#$%&'*+-/=?^_`{|}~]|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF])+)*)|((x22)((((x20|x09)*(x0dx0a))?(x20|x09)+)?(([x01-x08x0bx0cx0e-x1fx7f]|x21|[x23-x5b]|[x5d-x7e]|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF])|(\([x01-x09x0bx0cx0d-x7f]|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF]))))*(((x20|x09)*(x0dx0a))?(x20|x09)+)?(x22)))@((([a-z]|d|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF])|(([a-z]|d|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF])([a-z]|d|-|.|_|~|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF])*([a-z]|d|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF]))).)+(([a-z]|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF])|(([a-z]|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF])([a-z]|d|-|.|_|~|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF])*([a-z]|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF])))$/i.test(value);
                },
    
                // http://docs.jquery.com/Plugins/Validation/Methods/url
                url: function (value, element) {
                    // contributed by Scott Gonzalez: http://projects.scottsplayground.com/iri/
                    return this.optional(element) || /^(https?|s?ftp)://(((([a-z]|d|-|.|_|~|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF])|(%[da-f]{2})|[!$&'()*+,;=]|:)*@)?(((d|[1-9]d|1dd|2[0-4]d|25[0-5]).(d|[1-9]d|1dd|2[0-4]d|25[0-5]).(d|[1-9]d|1dd|2[0-4]d|25[0-5]).(d|[1-9]d|1dd|2[0-4]d|25[0-5]))|((([a-z]|d|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF])|(([a-z]|d|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF])([a-z]|d|-|.|_|~|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF])*([a-z]|d|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF]))).)+(([a-z]|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF])|(([a-z]|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF])([a-z]|d|-|.|_|~|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF])*([a-z]|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF]))).?)(:d*)?)(/((([a-z]|d|-|.|_|~|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF])|(%[da-f]{2})|[!$&'()*+,;=]|:|@)+(/(([a-z]|d|-|.|_|~|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF])|(%[da-f]{2})|[!$&'()*+,;=]|:|@)*)*)?)?(?((([a-z]|d|-|.|_|~|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF])|(%[da-f]{2})|[!$&'()*+,;=]|:|@)|[uE000-uF8FF]|/|?)*)?(#((([a-z]|d|-|.|_|~|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF])|(%[da-f]{2})|[!$&'()*+,;=]|:|@)|/|?)*)?$/i.test(value);
                },
    
                // http://docs.jquery.com/Plugins/Validation/Methods/date
                date: function (value, element) {
                    return this.optional(element) || !/Invalid|NaN/.test(new Date(value).toString());
                },
    
                // http://docs.jquery.com/Plugins/Validation/Methods/dateISO
                dateISO: function (value, element) {
                    return this.optional(element) || /^d{4}[/-]d{1,2}[/-]d{1,2}$/.test(value);
                },
    
                // http://docs.jquery.com/Plugins/Validation/Methods/number
                number: function (value, element) {
                    return this.optional(element) || /^-?(?:d+|d{1,3}(?:,d{3})+)?(?:.d+)?$/.test(value);
                },
    
                // http://docs.jquery.com/Plugins/Validation/Methods/digits
                digits: function (value, element) {
                    return this.optional(element) || /^d+$/.test(value);
                },
    
                // http://docs.jquery.com/Plugins/Validation/Methods/creditcard
                // based on http://en.wikipedia.org/wiki/Luhn
                creditcard: function (value, element) {
                    if (this.optional(element)) {
                        return "dependency-mismatch";
                    }
                    // accept only spaces, digits and dashes
                    if (/[^0-9 -]+/.test(value)) {
                        return false;
                    }
                    var nCheck = 0,
                        nDigit = 0,
                        bEven = false;
    
                    value = value.replace(/D/g, "");
    
                    for (var n = value.length - 1; n >= 0; n--) {
                        var cDigit = value.charAt(n);
                        nDigit = parseInt(cDigit, 10);
                        if (bEven) {
                            if ((nDigit *= 2) > 9) {
                                nDigit -= 9;
                            }
                        }
                        nCheck += nDigit;
                        bEven = !bEven;
                    }
    
                    return (nCheck % 10) === 0;
                },
    
                // http://docs.jquery.com/Plugins/Validation/Methods/minlength
                minlength: function (value, element, param) {
                    var length = $.isArray(value) ? value.length : this.getLength($.trim(value), element);
                    return this.optional(element) || length >= param;
                },
    
                // http://docs.jquery.com/Plugins/Validation/Methods/maxlength
                maxlength: function (value, element, param) {
                    var length = $.isArray(value) ? value.length : this.getLength($.trim(value), element);
                    return this.optional(element) || length <= param;
                },
    
                // http://docs.jquery.com/Plugins/Validation/Methods/rangelength
                rangelength: function (value, element, param) {
                    var length = $.isArray(value) ? value.length : this.getLength($.trim(value), element);
                    return this.optional(element) || (length >= param[0] && length <= param[1]);
                },
    
                // http://docs.jquery.com/Plugins/Validation/Methods/min
                min: function (value, element, param) {
                    return this.optional(element) || value >= param;
                },
    
                // http://docs.jquery.com/Plugins/Validation/Methods/max
                max: function (value, element, param) {
                    return this.optional(element) || value <= param;
                },
    
                // http://docs.jquery.com/Plugins/Validation/Methods/range
                range: function (value, element, param) {
                    return this.optional(element) || (value >= param[0] && value <= param[1]);
                },
    
                // http://docs.jquery.com/Plugins/Validation/Methods/equalTo
                equalTo: function (value, element, param) {
                    // bind to the blur event of the target in order to revalidate whenever the target field is updated
                    // TODO find a way to bind the event just once, avoiding the unbind-rebind overhead
                    var target = $(param);
                    if (this.settings.onfocusout) {
                        target.unbind(".validate-equalTo").bind("blur.validate-equalTo", function () {
                            $(element).valid();
                        });
                    }
                    return value === target.val();
                },
    
                // http://docs.jquery.com/Plugins/Validation/Methods/remote
                remote: function (value, element, param) {
                    if (this.optional(element)) {
                        return "dependency-mismatch";
                    }
    
                    var previous = this.previousValue(element);
                    if (!this.settings.messages[element.name]) {
                        this.settings.messages[element.name] = {};
                    }
                    previous.originalMessage = this.settings.messages[element.name].remote;
                    this.settings.messages[element.name].remote = previous.message;
    
                    param = typeof param === "string" && { url: param } || param;
    
                    if (previous.old === value) {
                        return previous.valid;
                    }
    
                    previous.old = value;
                    var validator = this;
                    this.startRequest(element);
                    var data = {};
                    data[element.name] = value;
                    $.ajax($.extend(true, {
                        url: param,
                        mode: "abort",
                        port: "validate" + element.name,
                        dataType: "json",
                        data: data,
                        success: function (response) {
                            validator.settings.messages[element.name].remote = previous.originalMessage;
                            var valid = response === true || response === "true";
                            if (valid) {
                                var submitted = validator.formSubmitted;
                                validator.prepareElement(element);
                                validator.formSubmitted = submitted;
                                validator.successList.push(element);
                                delete validator.invalid[element.name];
                                validator.showErrors();
                            } else {
                                var errors = {};
                                var message = response || validator.defaultMessage(element, "remote");
                                errors[element.name] = previous.message = $.isFunction(message) ? message(value) : message;
                                validator.invalid[element.name] = true;
                                validator.showErrors(errors);
                            }
                            previous.valid = valid;
                            validator.stopRequest(element, valid);
                        }
                    }, param));
                    return "pending";
                }
    
            }
    
        });
    
        // deprecated, use $.validator.format instead
        $.format = $.validator.format;
    
    }(jQuery));
    
    // ajax mode: abort
    // usage: $.ajax({ mode: "abort"[, port: "uniqueport"]});
    // if mode:"abort" is used, the previous request on that port (port can be undefined) is aborted via XMLHttpRequest.abort()
    (function ($) {
        var pendingRequests = {};
        // Use a prefilter if available (1.5+)
        if ($.ajaxPrefilter) {
            $.ajaxPrefilter(function (settings, _, xhr) {
                var port = settings.port;
                if (settings.mode === "abort") {
                    if (pendingRequests[port]) {
                        pendingRequests[port].abort();
                    }
                    pendingRequests[port] = xhr;
                }
            });
        } else {
            // Proxy ajax
            var ajax = $.ajax;
            $.ajax = function (settings) {
                var mode = ("mode" in settings ? settings : $.ajaxSettings).mode,
                    port = ("port" in settings ? settings : $.ajaxSettings).port;
                if (mode === "abort") {
                    if (pendingRequests[port]) {
                        pendingRequests[port].abort();
                    }
                    pendingRequests[port] = ajax.apply(this, arguments);
                    return pendingRequests[port];
                }
                return ajax.apply(this, arguments);
            };
        }
    }(jQuery));
    
    // provides delegate(type: String, delegate: Selector, handler: Callback) plugin for easier event delegation
    // handler is only called when $(event.target).is(delegate), in the scope of the jquery-object for event.target
    (function ($) {
        $.extend($.fn, {
            validateDelegate: function (delegate, type, handler) {
                return this.bind(type, function (event) {
                    var target = $(event.target);
                    if (target.is(delegate)) {
                        return handler.apply(target, arguments);
                    }
                });
            }
        });
    }(jQuery));
    jquery.validate.js

    这里的poshytip的皮肤文件不做任何修改,下载后可以直接引用,需要修改样式的单独修改poshytip对应皮肤文件吧。

    --jquery.poshytip.js

    --修改并添加对版本js的兼容

    --为showOn添加elemshow可以在验证错误时显示错误消息提示。

    /*
    * 修改时间:2016-04-20
     *修改人:Loyung
     *说明:
     *1.更新对JQuery9以上版本的兼容问题
     *2.参数showOn添加"elemshow",用作poshytip处理时使用。可以为任意元素创建showOn动作。
    */
    /*
     * Poshy Tip jQuery plugin v1.0
     * http://vadikom.com/tools/poshy-tip-jquery-plugin-for-stylish-tooltips/
     * Copyright 2010, Vasil Dinkov, http://vadikom.com/
     */
    /*$.browser这个api从jQuery1.9开始就正式废除,处理错误:Cannot read property ‘msie’ of undefined*/
    jQuery.browser = {}; (function () { jQuery.browser.msie = false; jQuery.browser.version = 0; if (navigator.userAgent.match(/MSIE ([0-9]+)./)) { jQuery.browser.msie = true; jQuery.browser.version = RegExp.$1; } })();
    
    (function($) {
    
        var tips = [],
            reBgImage = /^url(["']?([^"')]*)["']?);?$/i,
            rePNG = /.png$/i,
            ie6 = $.browser.msie && $.browser.version == 6;
    
        // make sure the tips' position is updated on resize
        function handleWindowResize() {
            $.each(tips, function() {
                this.refresh(true);
            });
        }
        $(window).resize(handleWindowResize);
    
        $.Poshytip = function (elm, options) {
            this.$elm = $(elm);
            this.opts = $.extend({}, $.fn.poshytip.defaults, options);
            this.$tip = $(['<div class="',this.opts.className,'">',
                    '<div class="tip-inner tip-bg-image"></div>',
                    '<div class="tip-arrow tip-arrow-top tip-arrow-right tip-arrow-bottom tip-arrow-left"></div>',
                '</div>'].join(''));
            this.$arrow = this.$tip.find('div.tip-arrow');
            this.$inner = this.$tip.find('div.tip-inner');
            this.disabled = false;
            this.init();
        };
    
        $.Poshytip.prototype = {
            init: function() {
                tips.push(this);
    
                // save the original title and a reference to the Poshytip object
                this.$elm.data('title.poshytip', this.$elm.attr('title'))
                    .data('poshytip', this);
    
                // hook element events
                switch (this.opts.showOn) {
                    case 'hover':
                        this.$elm.bind({
                            'mouseenter.poshytip': $.proxy(this.mouseenter, this),
                            'mouseleave.poshytip': $.proxy(this.mouseleave, this)
                        });
                        if (this.opts.alignTo == 'cursor')
                            this.$elm.bind('mousemove.poshytip', $.proxy(this.mousemove, this));
                        if (this.opts.allowTipHover)
                            this.$tip.hover($.proxy(this.clearTimeouts, this), $.proxy(this.hide, this));
                        break;
                    case 'focus':
                        this.$elm.bind({
                            'focus.poshytip': $.proxy(this.show, this),
                            'blur.poshytip': $.proxy(this.hide, this)
                        });
                        break;
                        //为指定元素tip提醒——刘自洋20160420
                    case 'elemshow':
                        this.showposition();
                        this.$tip.hover($.proxy(this.clearTimeouts, this), $.proxy(this.hide, this));
                        setTimeout($.proxy(this.hide, this),3000);
                        break;
                }
            },
            mouseenter: function(e) {
                if (this.disabled)
                    return true;
    
                this.clearTimeouts();
                this.$elm.attr('title', '');
                this.showTimeout = setTimeout($.proxy(this.show, this), this.opts.showTimeout);
            },
            mouseleave: function() {
                if (this.disabled)
                    return true;
    
                this.clearTimeouts();
                this.$elm.attr('title', this.$elm.data('title.poshytip'));
                this.hideTimeout = setTimeout($.proxy(this.hide, this), this.opts.hideTimeout);
            },
            mousemove: function(e) {
                if (this.disabled)
                    return true;
    
                this.eventX = e.pageX;
                this.eventY = e.pageY;
                if (this.opts.followCursor && this.$tip.data('active')) {
                    this.calcPos();
                    this.$tip.css({left: this.pos.l, top: this.pos.t});
                    if (this.pos.arrow)
                        this.$arrow[0].className = 'tip-arrow tip-arrow-' + this.pos.arrow;
                }
            },
            showposition: function (e) {
                if (this.disabled || this.$tip.data('active'))
                    return;
    
                this.opts.alignTo = 'target';
                this.calcPos();
                this.update();
               
                this.display();
            },
            show: function() {
                if (this.disabled || this.$tip.data('active'))
                    return;
    
                this.reset();
                this.update();
                this.display();
            },
            hide: function() {
                if (this.disabled || !this.$tip.data('active'))
                    return;
    
                this.display(true);
            },
            reset: function() {
                this.$tip.queue([]).detach().css('visibility', 'hidden').data('active', false);
                this.$inner.find('*').poshytip('hide');
                if (this.opts.fade)
                    this.$tip.css('opacity', this.opacity);
                this.$arrow[0].className = 'tip-arrow tip-arrow-top tip-arrow-right tip-arrow-bottom tip-arrow-left';
            },
            update: function(content) {
                if (this.disabled)
                    return;
    
                var async = content !== undefined;
                if (async) {
                    if (!this.$tip.data('active'))
                        return;
                } else {
                    content = this.opts.content;
                }
    
                this.$inner.contents().detach();
                var self = this;
                this.$inner.append(
                    typeof content == 'function' ?
                        content.call(this.$elm[0], function(newContent) {
                            self.update(newContent);
                        }) :
                        content == '[title]' ? this.$elm.data('title.poshytip') : content
                );
                
                this.refresh(async);
            },
            refresh: function(async) {
                if (this.disabled)
                    return;
    
                if (async) {
                    if (!this.$tip.data('active'))
                        return;
                    // save current position as we will need to animate
                    var currPos = {left: this.$tip.css('left'), top: this.$tip.css('top')};
                }
    
                // reset position to avoid text wrapping, etc.
                this.$tip.css({left: 0, top: 0}).appendTo(document.body);
    
                // save default opacity
                if (this.opacity === undefined)
                    this.opacity = this.$tip.css('opacity');
    
                // check for images - this code is here (i.e. executed each time we show the tip and not on init) due to some browser inconsistencies
                var bgImage = this.$tip.css('background-image').match(reBgImage),
                    arrow = this.$arrow.css('background-image').match(reBgImage);
    
                if (bgImage) {
                    var bgImagePNG = rePNG.test(bgImage[1]);
                    // fallback to background-color/padding/border in IE6 if a PNG is used
                    if (ie6 && bgImagePNG) {
                        this.$tip.css('background-image', 'none');
                        this.$inner.css({margin: 0, border: 0, padding: 0});
                        bgImage = bgImagePNG = false;
                    } else {
                        this.$tip.prepend('<table border="0" cellpadding="0" cellspacing="0"><tr><td class="tip-top tip-bg-image" colspan="2"><span></span></td><td class="tip-right tip-bg-image" rowspan="2"><span></span></td></tr><tr><td class="tip-left tip-bg-image" rowspan="2"><span></span></td><td></td></tr><tr><td class="tip-bottom tip-bg-image" colspan="2"><span></span></td></tr></table>')
                            .css({border: 0, padding: 0, 'background-image': 'none', 'background-color': 'transparent'})
                            .find('.tip-bg-image').css('background-image', 'url("' + bgImage[1] +'")').end()
                            .find('td').eq(3).append(this.$inner);
                    }
                    // disable fade effect in IE due to Alpha filter + translucent PNG issue
                    if (bgImagePNG && !$.support.opacity)
                        this.opts.fade = false;
                }
                // IE arrow fixes
                if (arrow && !$.support.opacity) {
                    // disable arrow in IE6 if using a PNG
                    if (ie6 && rePNG.test(arrow[1])) {
                        arrow = false;
                        this.$arrow.css('background-image', 'none');
                    }
                    // disable fade effect in IE due to Alpha filter + translucent PNG issue
                    this.opts.fade = false;
                }
    
                var $table = this.$tip.find('table');
                if (ie6) {
                    // fix min/max-width in IE6
                    this.$tip[0].style.width = '';
                    $table.width('auto').find('td').eq(3).width('auto');
                    var tipW = this.$tip.width(),
                        minW = parseInt(this.$tip.css('min-width')),
                        maxW = parseInt(this.$tip.css('max-width'));
                    if (!isNaN(minW) && tipW < minW)
                        tipW = minW;
                    else if (!isNaN(maxW) && tipW > maxW)
                        tipW = maxW;
                    this.$tip.add($table).width(tipW).eq(0).find('td').eq(3).width('100%');
                } else if ($table[0]) {
                    // fix the table width if we are using a background image
                    $table.width('auto').find('td').eq(3).width('auto').end().end().width(this.$tip.width()).find('td').eq(3).width('100%');
                }
                this.tipOuterW = this.$tip.outerWidth();
                this.tipOuterH = this.$tip.outerHeight();
    
                this.calcPos();
    
                // position and show the arrow image
                if (arrow && this.pos.arrow) {
                    this.$arrow[0].className = 'tip-arrow tip-arrow-' + this.pos.arrow;
                    this.$arrow.css('visibility', 'inherit');
                }
    
                if (async)
                    this.$tip.css(currPos).animate({left: this.pos.l, top: this.pos.t}, 200);
                else
                    this.$tip.css({left: this.pos.l, top: this.pos.t});
            },
            display: function(hide) {
                var active = this.$tip.data('active');
                if (active && !hide || !active && hide)
                    return;
    
                this.$tip.stop();
                if ((this.opts.slide && this.pos.arrow || this.opts.fade) && (hide && this.opts.hideAniDuration || !hide && this.opts.showAniDuration)) {
                    var from = {}, to = {};
                    // this.pos.arrow is only undefined when alignX == alignY == 'center' and we don't need to slide in that rare case
                    if (this.opts.slide && this.pos.arrow) {
                        var prop, arr;
                        if (this.pos.arrow == 'bottom' || this.pos.arrow == 'top') {
                            prop = 'top';
                            arr = 'bottom';
                        } else {
                            prop = 'left';
                            arr = 'right';
                        }
                        var val = parseInt(this.$tip.css(prop));
                        from[prop] = val + (hide ? 0 : this.opts.slideOffset * (this.pos.arrow == arr ? -1 : 1));
                        to[prop] = val + (hide ? this.opts.slideOffset * (this.pos.arrow == arr ? 1 : -1) : 0);
                    }
                    if (this.opts.fade) {
                        from.opacity = hide ? this.$tip.css('opacity') : 0;
                        to.opacity = hide ? 0 : this.opacity;
                    }
                    this.$tip.css(from).animate(to, this.opts[hide ? 'hideAniDuration' : 'showAniDuration']);
                }
                hide ? this.$tip.queue($.proxy(this.reset, this)) : this.$tip.css('visibility', 'inherit');
                this.$tip.data('active', !active);
            },
            disable: function() {
                this.reset();
                this.disabled = true;
            },
            enable: function() {
                this.disabled = false;
            },
            destroy: function() {
                this.reset();
                this.$tip.remove();
                this.$elm.unbind('poshytip').removeData('title.poshytip').removeData('poshytip');
                tips.splice($.inArray(this, tips), 1);
            },
            clearTimeouts: function() {
                if (this.showTimeout) {
                    clearTimeout(this.showTimeout);
                    this.showTimeout = 0;
                }
                if (this.hideTimeout) {
                    clearTimeout(this.hideTimeout);
                    this.hideTimeout = 0;
                }
            },
            calcPos: function() {
                var pos = {l: 0, t: 0, arrow: ''},
                    $win = $(window),
                    win = {
                        l: $win.scrollLeft(),
                        t: $win.scrollTop(),
                        w: $win.width(),
                        h: $win.height()
                    }, xL, xC, xR, yT, yC, yB;
                if (this.opts.alignTo == 'cursor') {
                    xL = xC = xR = this.eventX;
                    yT = yC = yB = this.eventY;
                } else { // this.opts.alignTo == 'target'
                    var elmOffset = this.$elm.offset(),
                        elm = {
                            l: elmOffset.left,
                            t: elmOffset.top,
                            w: this.$elm.outerWidth(),
                            h: this.$elm.outerHeight()
                        };
                    xL = elm.l + (this.opts.alignX != 'inner-right' ? 0 : elm.w);    // left edge
                    xC = xL + Math.floor(elm.w / 2);                // h center
                    xR = xL + (this.opts.alignX != 'inner-left' ? elm.w : 0);    // right edge
                    yT = elm.t + (this.opts.alignY != 'inner-bottom' ? 0 : elm.h);    // top edge
                    yC = yT + Math.floor(elm.h / 2);                // v center
                    yB = yT + (this.opts.alignY != 'inner-top' ? elm.h : 0);    // bottom edge
                }
    
                // keep in viewport and calc arrow position
                switch (this.opts.alignX) {
                    case 'right':
                    case 'inner-left':
                        pos.l = xR + this.opts.offsetX;
                        if (pos.l + this.tipOuterW > win.l + win.w)
                            pos.l = win.l + win.w - this.tipOuterW;
                        if (this.opts.alignX == 'right' || this.opts.alignY == 'center')
                            pos.arrow = 'left';
                        break;
                    case 'center':
                        pos.l = xC - Math.floor(this.tipOuterW / 2);
                        if (pos.l + this.tipOuterW > win.l + win.w)
                            pos.l = win.l + win.w - this.tipOuterW;
                        else if (pos.l < win.l)
                            pos.l = win.l;
                        break;
                    default: // 'left' || 'inner-right'
                        pos.l = xL - this.tipOuterW - this.opts.offsetX;
                        if (pos.l < win.l)
                            pos.l = win.l;
                        if (this.opts.alignX == 'left' || this.opts.alignY == 'center')
                            pos.arrow = 'right';
                }
                switch (this.opts.alignY) {
                    case 'bottom':
                    case 'inner-top':
                        pos.t = yB + this.opts.offsetY;
                        // 'left' and 'right' need priority for 'target'
                        if (!pos.arrow || this.opts.alignTo == 'cursor')
                            pos.arrow = 'top';
                        if (pos.t + this.tipOuterH > win.t + win.h) {
                            pos.t = yT - this.tipOuterH - this.opts.offsetY;
                            if (pos.arrow == 'top')
                                pos.arrow = 'bottom';
                        }
                        break;
                    case 'center':
                        pos.t = yC - Math.floor(this.tipOuterH / 2);
                        if (pos.t + this.tipOuterH > win.t + win.h)
                            pos.t = win.t + win.h - this.tipOuterH;
                        else if (pos.t < win.t)
                            pos.t = win.t;
                        break;
                    default: // 'top' || 'inner-bottom'
                        pos.t = yT - this.tipOuterH - this.opts.offsetY;
                        // 'left' and 'right' need priority for 'target'
                        if (!pos.arrow || this.opts.alignTo == 'cursor')
                            pos.arrow = 'bottom';
                        if (pos.t < win.t) {
                            pos.t = yB + this.opts.offsetY;
                            if (pos.arrow == 'bottom')
                                pos.arrow = 'top';
                        }
                }
                this.pos = pos;
            }
        };
    
        $.fn.poshytip = function(options){
            if (typeof options == 'string') {
                return this.each(function() {
                    var poshytip = $(this).data('poshytip');
                    if (poshytip && poshytip[options])
                        poshytip[options]();
                });
            }
    
            var opts = $.extend({}, $.fn.poshytip.defaults, options);
    
            // generate CSS for this tip class if not already generated
            if (!$('#poshytip-css-' + opts.className)[0])
                $(['<style id="poshytip-css-',opts.className,'" type="text/css">',
                    'div.',opts.className,'{visibility:hidden;position:absolute;top:0;left:0;}',
                    'div.',opts.className,' table, div.',opts.className,' td{margin:0;font-family:inherit;font-size:inherit;font-weight:inherit;font-style:inherit;font-variant:inherit;}',
                    'div.',opts.className,' td.tip-bg-image span{display:block;font:1px/1px sans-serif;height:',opts.bgImageFrameSize,'px;',opts.bgImageFrameSize,'px;overflow:hidden;}',
                    'div.',opts.className,' td.tip-right{background-position:100% 0;}',
                    'div.',opts.className,' td.tip-bottom{background-position:100% 100%;}',
                    'div.',opts.className,' td.tip-left{background-position:0 100%;}',
                    'div.',opts.className,' div.tip-inner{background-position:-',opts.bgImageFrameSize,'px -',opts.bgImageFrameSize,'px;}',
                    'div.',opts.className,' div.tip-arrow{visibility:hidden;position:absolute;overflow:hidden;font:1px/1px sans-serif;}',
                '</style>'].join('')).appendTo('head');
    
            return this.each(function() {
                new $.Poshytip(this, opts);
            });
        }
    
        // default settings
        $.fn.poshytip.defaults = {
            content:         '[title]',    // content to display ('[title]', 'string', element, function(updateCallback){...}, jQuery)
            className:        'tip-twitter',    // class for the tips
            bgImageFrameSize:    10,        // 提示框背景图片的大小
            showTimeout:        500,        // timeout before showing the tip (in milliseconds 1000 == 1 second)
            hideTimeout:        100,        // timeout before hiding the tip
            showOn:            'hover',    // 触发何种事件显示提示框 ('hover', 'focus', 'none') - use 'none' to trigger it manually
            alignTo: 'target',    // 设置箭头位置('cursor', 'target')
            alignX: 'right',    // 水平对齐相对于鼠标光标或目标元素
                                // ('right', 'center', 'left', 'inner-left', 'inner-right') - 'inner-*' matter if alignTo:'target'
            alignY: 'center',        // 垂直对齐相对于鼠标光标或目标元素
                                // ('bottom', 'center', 'top', 'inner-bottom', 'inner-top') - 'inner-*' matter if alignTo:'target'
            offsetX:        12,        // 设置提示框横向偏移 - doesn't matter if alignX:'center'
            offsetY:        18,        //     设置提示框纵向偏移 - doesn't matter if alignY:'center'
            allowTipHover:        true,        // 当鼠标悬在tip上时,不隐藏tip- matters only if showOn:'hover'
            followCursor:        false,        //提示跟随光标移动- matters only if showOn:'hover' and alignTo:'cursor'
            fade:             true,        // 使用fade动画
            slide:             true,        // 使用slide动画
            slideOffset:         8,        // slide动画相抵消
            showAniDuration:     300,        // 显示动画时长 - set to 0 if you don't want show animation
            hideAniDuration: 300,        //  隐藏动画的持续时间 - set to 0 if you don't want hide animation
            refreshAniDuration:1000   //异步更新提示时,动画的持续时间
        };
    
    })(jQuery);
    jquery.poshytip.js

    我们自己重写定义的一些方法在这里比较综合,但也是结合validation和poshytip的关键。

    --jquery.validate.tip.js

    /*
    *创建人:刘自洋
    *创建时间:2017-07-11
    *说明:该文件对jquery.validate几种验证事件进行重配置,以便于做消息Tip提醒。是validate与poshytip结合的关键
    */
    $.validator.setDefaults({
        /*关闭键盘输入时的实时校验*/
        onkeyup: function (element) {
            //$(element).poshytip({ content: "键盘输入", showOn: 'elemshow' });
        },
        /*校验成功/
        success: function (element) {
            //$(element).poshytip({ content: "校验成功", showOn: 'elemshow' });
        },
        /*获得焦点后执行*/
        onfocusin: function (element) {
            this.lastActive = element;
            this.addWrapper(this.errorsFor(element)).hide();
            var focusintip = $(element).attr('focusin');
            if (focusintip && $(element).parent().children(".focusin").length === 0) {
                //$(element).parent().poshytip({ content: focusintip, showOn: 'elemshow' });//tip光标选中后提示
            }
          
            // Hide error label and remove error class on focus if enabled
            if (this.settings.focusCleanup) {
                if (this.settings.unhighlight) {
                    this.settings.unhighlight.call(this, element, this.settings.errorClass, this.settings.validClass);
                }
                this.hideThese(this.errorsFor(element));
            }
        },
        /*焦点离开*/
        onfocusout: function (element) {
            $(element).valid();
            if ( !this.checkable( element ) && ( element.name in this.submitted || !this.optional( element ) ) ) {
                this.element( element );
            }
        },
        errorPlacement: function (error, element) {//错误信息显示位置
            if ($(error).text() != "") {
                $(element).poshytip({ content: $(error).text(), showOn: 'elemshow' });
                //$(element).parent().poshytip({ content: $(error).text(), showOn: 'elemshow' });//父容器追加提示
            }
        },
        submitHandler: function (form) {//表单验证完成后提交表单
            form.submit();
            return false;
        }
    });
    /*指定表单加上验证才能提交*/
    $(function () {
        $("form").validate();
    });
    /*修改默认validate提示信息*/
    $.extend($.validator.messages, {
        required: "必填字段",
        remote: "请修正该字段",
        email: "请输入正确格式的电子邮件",
        url: "请输入合法的网址",
        date: "请输入合法的日期",
        dateISO: "请输入合法的日期 (ISO).",
        number: "请输入合法的数字",
        digits: "只能输入整数",
        creditcard: "请输入合法的信用卡号",
        equalTo: "请再次输入相同的值",
        accept: "请输入拥有合法后缀名的字符串",
        maxlength: $.validator.format("请输入一个长度最多是 {0} 的字符串"),
        minlength: $.validator.format("请输入一个长度最少是 {0} 的字符串"),
        rangelength: $.validator.format("请输入一个长度介于 {0} 和 {1} 之间的字符串"),
        range: $.validator.format("请输入一个介于 {0} 和 {1} 之间的值"),
        max: $.validator.format("请输入一个最大为 {0} 的值"),
        min: $.validator.format("请输入一个最小为 {0} 的值"),
    });
    jquery.validate.tip.js

    到这里已经基本算是完成了,后面会陆续优化一些bug。

    疑难杂症分析:

    1.如果提交表单时出现只验证第一个设置验证的元素,那么请检查标签是否配置了name属性或者配置了name属性相同了。

    如果大家遇到哪些存在的bug都可以交流修改。

    关于validation的具体详解对于新手来说可能好需要进一步了解,可以查看REX HE的博客 jQuery验证控件jquery.validate.js使用说明+中文API

  • 相关阅读:
    利用相关的Aware接口
    java 值传递和引用传递。
    权限控制框架Spring Security 和Shiro 的总结
    优秀代码养成
    Servlet 基础知识
    leetcode 501. Find Mode in Binary Search Tree
    leetcode 530. Minimum Absolute Difference in BST
    leetcode 543. Diameter of Binary Tree
    leetcode 551. Student Attendance Record I
    leetcode 563. Binary Tree Tilt
  • 原文地址:https://www.cnblogs.com/loyung/p/7152042.html
Copyright © 2011-2022 走看看