項目中使用到了這個插件,抽了個空,看了一下。
(function($){ var method ={} $.fn.validationEngine = function(){} $.validationEngine = {} $(function(){$.validationEngine.defaults.promptPosition = methods.isRTL()?'topLeft':"topRight"}); })(jQuery)
看一下結構,還是比較清晰的。jQuery的dom對象直接調用就行了,下面我拿jQuery官方上的一個例子來說明
看一下例子:
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-type" content="text/html; charset=utf-8" /> <title>JQuery Validation Engine</title> <link rel="stylesheet" href="../css/validationEngine.jquery.css" type="text/css"/> <link rel="stylesheet" href="../css/template.css" type="text/css"/> <script src="../js/jquery-1.8.2.min.js" type="text/javascript"> </script> <script src="../js/languages/jquery.validationEngine-en.js" type="text/javascript" charset="utf-8"> </script> <script src="../js/jquery.validationEngine.js" type="text/javascript" charset="utf-8"> </script> <script> jQuery(document).ready(function(){ // binds form submission and fields to the validation engine jQuery("#formID").validationEngine();//初始化 }); </script> </head> <body> <p> This demonstration shows the prompt position adjustments <br/> </p> <div id="test" class="test" style="width:150px;">This is a div element</div> <form id="formID" class="formular" method="post" action=""> <fieldset> <label> <span>Fields are required : </span> <input value="" class="validate[required] text-input" type="text" name="req" id="req" data-prompt-position="topRight:-70" /> </label> <label> <input value="" class="validate[required] text-input" type="text" name="req2" id="req2" data-prompt-position="topRight:-30" /> </label> <label> <span>Additional info:</span> <textarea class="validate[required]" id="add" style="width:250px;height:50px;" data-prompt-position="bottomRight:-70,3"></textarea> </label> <br/> <br/> </fieldset> <input class="submit" type="submit" value="Validate & Send the form!"/><hr/> </form> </body> </html>
可以看到頁面直接調用了validationEngine方法。
$.fn.validationEngine = function(method) { var form = $(this);//獲取form對象 if(!form[0]) return form; // stop here if the form does not exist 如果不是jQuery的dom對象則返回 if (typeof(method) == 'string' && method.charAt(0) != '_' && methods[method]) { // make sure init is called once if(method != "showPrompt" && method != "hide" && method != "hideAll") methods.init.apply(form);
return methods[method].apply(form, Array.prototype.slice.call(arguments, 1));//支持傳入原型上的方法執行該方法 } else if (typeof method == 'object' || !method) { //該例子中method為undefined // default constructor with or without arguments methods.init.apply(form, arguments);//調用方法對象中的init方法 return methods.attach.apply(form);//調用方法對象中的attach方法 } else { $.error('Method ' + method + ' does not exist in jQuery.validationEngine'); } }
這里,可以看到如果想讓驗證控件不能工作,在validationEngine方法中傳入三種參數,showPrompt,hide,hideAll。等會后面分析,會看到,只要代碼處調用傳入這個三個參數,插件都將不會工作,因為其中的methods.init,attach方法沒有被執行,初始化所導致的結果。其實,如果你傳入像'1'這樣的參數,插件一樣不會工作,不過它會報錯,走入最后一個判斷,調用$.error拋出異常。
以下是引擎的工作流程

在使用jquery.validationEngine.js時還需要一個js文件:包括許多語言包,這里我們就用en包做個示范,引入jQuery.validationEngine-en.js
(function($){ $.fn.validationEngineLanguage = function(){ }; $.validationEngineLanguage = { newLang: function(){ $.validationEngineLanguage.allRules ={} } }; $.validationEngineLanguage.newLang(); })(jQuery);
validationEngineLanguage方法直接添加到jquery原型上,這里默認沒有初始化任何東西,實際開發中,根據需求做適當修改,代碼中默認執行一遍$.validationEngineLanguage.newLang(),讓$.validationEngineLanguage擁有allRules屬性,留心看一下這個allRules的內容
"required": { // Add your regex rules here, you can take telephone as an example
"regex": "none",
"alertText": "* This field is required",
"alertTextCheckboxMultiple": "* Please select an option",
"alertTextCheckboxe": "* This checkbox is required",
"alertTextDateRange": "* Both date range fields are required"
}
"phone": {
// credit: jquery.h5validate.js / orefalo
"regex": /^([\+][0-9]{1,3}[\ \.\-])?([\(]{1}[0-9]{2,6}[\)])?([0-9\ \.\-\/]{3,20})((x|ext|extension)[\ ]?[0-9]{1,4})?$/,
"alertText": "* Invalid phone number"
}
....
類似頁面寫入一個required,這個字符串會關聯許多信息,包括為空的彈出信息,phone這個字符串則關聯了匹配的正則和彈出的錯誤信息。如果需要添加新的匹配功能,這里是可以添加的。
Method.init
init: function(options) { var form = this; if (!form.data('jqv') || form.data('jqv') == null ) { options = methods._saveOptions(form, options); // bind all formError elements to close on click $(document).on("click", ".formError", function() {//讓html標簽去監聽擁有formError類的元素綁定click事件 $(this).fadeOut(150, function() {//實現淡出效果 // remove prompt once invisible $(this).parent('.formErrorOuter').remove();//刪除其父節點 $(this).remove();//刪除本身節點 }); }); } return this; }
這里.formError是彈出的錯誤信息的類,這里在init方法中給這些彈出信息添加了click事件,用於關閉它們。
Method._saveOptions
_saveOptions: function(form, options) { // is there a language localisation ? if ($.validationEngineLanguage) var allRules = $.validationEngineLanguage.allRules; else //這里的else語句就是else下面的緊跟它的第一句代碼,不推薦這樣寫。 $.error("jQuery.validationEngine rules are not loaded, plz add localization files to the page"); // --- Internals DO NOT TOUCH or OVERLOAD --- // validation rules and i18 $.validationEngine.defaults.allrules = allRules; var userOptions = $.extend(true,{},$.validationEngine.defaults,options);//保存錯誤規則 form.data('jqv', userOptions);//將userOptions信息保存在jqv屬性中 return userOptions; }
主要是導入jQuery.validationEngine-en.js的內容,將信息保存到jqv屬性中,最后返回該信息。
Method.attach
attach: function(userOptions) {//調用者是被驗證的form var form = this; var options; if(userOptions) options = methods._saveOptions(form, userOptions); else options = form.data('jqv');//讀取form的jqv信息 //尋找data-validation-engine屬性中擁有validate的標簽 options.validateAttribute = (form.find("[data-validation-engine*=validate]").length) ? "data-validation-engine" : "class"; if (options.binded) { //綁定blur事件,如果沒有data-validation-engine屬性的,那必須擁有包含validate字符串的類,且不是checkbox,radio或者是類datepicker(時間控件)的元素將擁有blur事件 //為checkbox,radio必須要有validate的控件綁定click事件 //單獨為時間控件綁定blur時間,傳入{'delay':300} // delegate fields form.on(options.validationEventTrigger, "["+options.validateAttribute+"*=validate]:not([type=checkbox]):not([type=radio]):not(.datepicker)", methods._onFieldEvent); form.on("click", "["+options.validateAttribute+"*=validate][type=checkbox],["+options.validateAttribute+"*=validate][type=radio]", methods._onFieldEvent); form.on(options.validationEventTrigger,"["+options.validateAttribute+"*=validate][class*=datepicker]", {"delay": 300}, methods._onFieldEvent); } if (options.autoPositionUpdate) { $(window).bind("resize", { "noAnimation": true, "formElem": form }, methods.updatePromptsPosition); } //為擁有data-validation-engine-skip屬性的a標簽和button,input標簽,類擁有validate-skip字段的a標簽和button,input標簽綁定click事件。 form.on("click","a[data-validation-engine-skip], a[class*='validate-skip'], button[data-validation-engine-skip], button[class*='validate-skip'], input[data-validation-engine-skip], input[class*='validate-skip']", methods._submitButtonClick); form.removeData('jqv_submitButton');//刪除jqv_submitButton信息 // bind form.submit form.on("submit", methods._onSubmitEvent);//綁定submit return this; }
綁定了基本上控件的觸發事件,這里還要提到的是在我們提交form的時候,會觸發插件內容的submit的監聽事件,實際上插件會在你提交之前再去幫你檢查一遍控件。如果全滿足條件了,則將會提交。可以看到很多的控件基本上綁定觸發的事件都是method._onFieldEvent方法

這里我們拿_onFieldEvent來看一下。
_onFieldEvent: function(event) { var field = $(this); var form = field.closest('form, .validationEngineContainer');//找到form對象 var options = form.data('jqv');//讀取jqv屬性信息 options.eventTrigger = "field"; // validate the current field window.setTimeout(function() { methods._validateField(field, options); if (options.InvalidFields.length == 0 && options.onFieldSuccess) { options.onFieldSuccess(); } else if (options.InvalidFields.length > 0 && options.onFieldFailure) { options.onFieldFailure(); } }, (event.data) ? event.data.delay : 0); }
將執行函數放入setTimeout中,這里考慮到一個datepicker插件的問題。
這里_validateField的全部代碼就不貼了,分析一下里面幾塊重要的部分
var rulesParsing = field.attr(options.validateAttribute);//獲取控件的類或者data-validation-engine屬性 var getRules = /validate\[(.*)\]/.exec(rulesParsing);//var getRules = rulesParsing.match(/validate\[(.*)\]/);數組中第一個是匹配整條正則,第二個成員是匹配子表達式 if (!getRules) return false;//如果數組為空,表示不匹配,則返回 var str = getRules[1];//獲取子表達式匹配的內容 var rules = str.split(/\[|,|\]/);//將以類似[,],','分開字符串,考慮validate[]中嵌套多個內容 for (var i = 0; i < rules.length; i++) { rules[i] = rules[i].replace(" ", "");//去掉空白 // Remove any parsing errors if (rules[i] === '') { delete rules[i];//刪除空的規則 } }
這里是對rules的解析,將原來的例子修改一下
<input value="" class="validate[required,custom[email]] text-input" type="text" name="req" id="req" data-prompt-position="topRight:-70" />
這里為什么不直接寫validate[required,email],或者是validate[required,xx[email]](xx不是custom)了?帶着這些疑問,我們來看一下源碼。
源碼中getRules使用的是exec來獲取匹配的結果文本,這里的匹配結果是[validate[reuqired,custom[email]] text-input,required,custom[email]]。再看str,它的值為required,custom[email],再經過split分隔,rule將變為[required,custom,email,''],最后經過for循環遍歷將將空字符串的成員delete掉,最后結果是[required,custom,email,undefined]。至於為什么要加這個custom,不急,我們慢慢看。
其實這個插件將控件需要執行的驗證規則都寫在了class類中,也就是說我們在validate[]中寫了一些規則,那么控件將會執行這些規則,其實required則表示為必填,email則表示為輸入email格式。兩者必須同時滿足。
源碼下面會再經歷一次for循環,但是注意,這里多了一個switch判斷,將rules中有的規則一一過濾出來,執行相應的方法。如果你留心看一下這個switch,你就會發現,這里面根本沒有email的選擇類型,所以說你直接在頁面上寫成validate[required,email]是不會有效果的。我們可以看到有custom的判斷。進入看一下這個custom,到底是什么
case "custom": errorMsg = methods._getErrorMessage(form, field, rules[i], rules, i, options, methods._custom); break;
_getErrorMessage這個方法說白了,就是獲取錯誤信息,執行傳入的回相對應的調函數
_getErrorMessage:function (form, field, rule, rules, i, options, originalValidationMethod) { // If we are using the custon validation type, build the index for the rule. // Otherwise if we are doing a function call, make the call and return the object // that is passed back. var rule_index = jQuery.inArray(rule, rules);//確定rule在rules中的位置 if (rule === "custom" || rule === "funcCall") { var custom_validation_type = rules[rule_index + 1]; rule = rule + "[" + custom_validation_type + "]"; // Delete the rule from the rules array so that it doesn't try to call the // same rule over again delete(rules[rule_index]); } // Change the rule to the composite rule, if it was different from the original var alteredRule = rule; var element_classes = (field.attr("data-validation-engine")) ? field.attr("data-validation-engine") : field.attr("class"); var element_classes_array = element_classes.split(" "); // Call the original validation method. If we are dealing with dates or checkboxes, also pass the form var errorMsg; if (rule == "future" || rule == "past" || rule == "maxCheckbox" || rule == "minCheckbox") { errorMsg = originalValidationMethod(form, field, rules, i, options); } else { errorMsg = originalValidationMethod(field, rules, i, options);//執行回調函數,獲得錯誤信息 } // If the original validation method returned an error and we have a custom error message, // return the custom message instead. Otherwise return the original error message. if (errorMsg != undefined) { var custom_message = methods._getCustomErrorMessage($(field), element_classes_array, alteredRule, options); if (custom_message) errorMsg = custom_message; } return errorMsg; }
下面就是method._custom方法
_custom: function(field, rules, i, options) { var customRule = rules[i + 1]; var rule = options.allrules[customRule]; var fn; if(!rule) { //規則沒有,則提示返回 alert("jqv:custom rule not found - "+customRule); return; } if(rule["regex"]) { var ex=rule.regex;//獲取相應正則 if(!ex) { alert("jqv:custom regex not found - "+customRule); return; } var pattern = new RegExp(ex);//轉成正則表達式 if (!pattern.test(field.val())) return options.allrules[customRule].alertText;//匹配正則 } else if(rule["func"]) { //自定義方法驗證,根據返回結果判斷 fn = rule["func"]; if (typeof(fn) !== "function") { alert("jqv:custom parameter 'function' is no function - "+customRule); return; } if (!fn(field, rules, i, options)) return options.allrules[customRule].alertText; } else { alert("jqv:custom type not allowed "+customRule); return; } }
其實看到這里,我們大致明白了,核心還是調用了rule['regex']的正則表達式。其實換了思維想一下,如果我們把email直接寫在switch中,那其實會有幾十種,甚至是幾百種情況,那一起寫在switch中明顯是不明智的,這個custom相當於做了個簡單的帶頭作用,將這些更細節的驗證判斷,統統掛上custom的名,再進行驗證。那么這個_required其實很類似
_required: function(field, rules, i, options, condRequired) { switch (field.prop("type")) {//取觸發控件的類型,注意這里attr,css都不能完全取到,這里作者使用它的是prop case "text": case "password": case "textarea": case "file": case "select-one": case "select-multiple": default: var field_val = $.trim( field.val() );//去除輸入的空格符 var dv_placeholder = $.trim( field.attr("data-validation-placeholder") ); var placeholder = $.trim( field.attr("placeholder") ); if ( ( !field_val ) || ( dv_placeholder && field_val == dv_placeholder ) || ( placeholder && field_val == placeholder ) ) { return options.allrules[rules[i]].alertText;//返回不填的錯誤信息 } break; case "radio": case "checkbox": // new validation style to only check dependent field if (condRequired) { if (!field.attr('checked')) { return options.allrules[rules[i]].alertTextCheckboxMultiple; } break; } // old validation style var form = field.closest("form, .validationEngineContainer"); var name = field.attr("name"); if (form.find("input[name='" + name + "']:checked").size() == 0) { if (form.find("input[name='" + name + "']:visible").size() == 1) return options.allrules[rules[i]].alertTextCheckboxe; else return options.allrules[rules[i]].alertTextCheckboxMultiple; } break; } }
這些方法進過驗證之后,如果不滿足情況,將會獲得錯誤信息
在流程圖的最后,整個插件的_showPrompt和_buildPrompt方法主要是生成頁面提示
_showPrompt: function(field, promptText, type, ajaxed, options, ajaxform) { var prompt = methods._getPrompt(field); // The ajax submit errors are not see has an error in the form, // When the form errors are returned, the engine see 2 bubbles, but those are ebing closed by the engine at the same time // Because no error was found befor submitting if(ajaxform) prompt = false; // Check that there is indded text if($.trim(promptText)){ if (prompt) methods._updatePrompt(field, prompt, promptText, type, ajaxed, options); else methods._buildPrompt(field, promptText, type, ajaxed, options); } }
看一下_buildPrompt方法
_buildPrompt: function(field, promptText, type, ajaxed, options) { // create the prompt var prompt = $('<div>'); prompt.addClass(methods._getClassName(field.attr("id")) + "formError"); // add a class name to identify the parent form of the prompt prompt.addClass("parentForm"+methods._getClassName(field.closest('form, .validationEngineContainer').attr("id"))); prompt.addClass("formError");//制作錯誤提示框,放上相應的class,這些class之前被監聽過事件 switch (type) { case "pass": prompt.addClass("greenPopup"); break; case "load": prompt.addClass("blackPopup"); break; default: /* it has error */ //alert("unknown popup type:"+type); } if (ajaxed) prompt.addClass("ajaxed"); // create the prompt content var promptContent = $('<div>').addClass("formErrorContent").html(promptText).appendTo(prompt);//將錯誤信息放在div中插入prompt中 // determine position type var positionType=field.data("promptPosition") || options.promptPosition; // create the css arrow pointing at the field // note that there is no triangle on max-checkbox and radio if (options.showArrow) { var arrow = $('<div>').addClass("formErrorArrow"); //prompt positioning adjustment support. Usage: positionType:Xshift,Yshift (for ex.: bottomLeft:+20 or bottomLeft:-20,+10) if (typeof(positionType)=='string') { var pos=positionType.indexOf(":"); if(pos!=-1) positionType=positionType.substring(0,pos); } switch (positionType) {//生成小三角 case "bottomLeft": case "bottomRight": prompt.find(".formErrorContent").before(arrow); arrow.addClass("formErrorArrowBottom").html('<div class="line1"><!-- --></div><div class="line2"><!-- --></div><div class="line3"><!-- --></div><div class="line4"><!-- --></div><div class="line5"><!-- --></div><div class="line6"><!-- --></div><div class="line7"><!-- --></div><div class="line8"><!-- --></div><div class="line9"><!-- --></div><div class="line10"><!-- --></div>'); break; case "topLeft": case "topRight": arrow.html('<div class="line10"><!-- --></div><div class="line9"><!-- --></div><div class="line8"><!-- --></div><div class="line7"><!-- --></div><div class="line6"><!-- --></div><div class="line5"><!-- --></div><div class="line4"><!-- --></div><div class="line3"><!-- --></div><div class="line2"><!-- --></div><div class="line1"><!-- --></div>'); prompt.append(arrow); break; } } // Add custom prompt class if (options.addPromptClass) prompt.addClass(options.addPromptClass); // Add custom prompt class defined in element var requiredOverride = field.attr('data-required-class'); if(requiredOverride !== undefined) { prompt.addClass(requiredOverride); } else { if(options.prettySelect) { if($('#' + field.attr('id')).next().is('select')) { var prettyOverrideClass = $('#' + field.attr('id').substr(options.usePrefix.length).substring(options.useSuffix.length)).attr('data-required-class'); if(prettyOverrideClass !== undefined) { prompt.addClass(prettyOverrideClass); } } } } prompt.css({ "opacity": 0 }); if(positionType === 'inline') { prompt.addClass("inline"); if(typeof field.attr('data-prompt-target') !== 'undefined' && $('#'+field.attr('data-prompt-target')).length > 0) { prompt.appendTo($('#'+field.attr('data-prompt-target'))); } else { field.after(prompt); } } else { field.before(prompt);//在觸發控件的前面插入 } var pos = methods._calculatePosition(field, prompt, options); prompt.css({ 'position': positionType === 'inline' ? 'relative' : 'absolute', "top": pos.callerTopPosition, "left": pos.callerleftPosition, "marginTop": pos.marginTopSize, "opacity": 0 }).data("callerField", field); if (options.autoHidePrompt) { setTimeout(function(){ prompt.animate({ "opacity": 0 },function(){ prompt.closest('.formErrorOuter').remove(); prompt.remove(); }); }, options.autoHideDelay); } return prompt.animate({ "opacity": 0.87 //動畫效果 }); }
生成的小三角的實現方式比較特別。。將錯誤內容放入div中,將生成div插在被觸發控件的前面,並且為div加上class,為什么加,在init方法中,我們已經為這類class添加了click事件,功能是點擊能夠刪除它們。
以上完成一遍簡單的驗證過程。
另外,這個插件還有一個強大的功能,就是提供的ajax驗證。
<input type="text" name="id" id="id" class="validate[required,ajax[ajaxUserCallPhp]]"/>
以上是頁面需要添加的東西,因為關聯到ajaxUserCallPhp,我們修改一下jQuery.validationEngine-en.js,找到ajaxUserCallPhp
"ajaxUserCallPhp": { "url": "../jsp/login_check.jsp",//修改成你要跳轉的路徑,或者你自己新寫一個關聯對象 // you may want to pass extra data on the ajax call "extraData": "name=eric", // if you provide an "alertTextOk", it will show as a green prompt when the field validates "alertTextOk": "* This username is available", "alertText": "* This user is already taken", "alertTextLoad": "* Validating, please wait" }
這里源碼是php,我這里暫時就修改為jsp頁面。
String yc = (String)request.getParameter("fieldValue");
String elementId = (String)request.getParameter("fieldId");
注意后台如何獲取ajax請求里的值,這里你使用抓包之類的工具就可以清楚看到url上類似拼接有fieldValue=xx&fieldId=xx,所以后台采用如此接住傳過來的參數,查詢數據庫,判斷是否有用戶名,最后往ajax返回信息時
String json = "{\"0\":\""+elementId+"\",\"1\":\""+da+"\",\"2\":\"yc\"}";
out.println(json);
這里我簡單的使用拼接的方式傳遞了json格式的數據,到時候,大家根據實際情況做修改。
嘗試去找method里的_ajax方法,可以發現這個_ajax方法其實調用了$.ajax的方法提交請求,注意在提交之前,經過了兩次for循環,主要是分解extraData,將信息組裝成json格式傳入data中,最后將data放入請求中根據url發送到后台。關於$.ajax,會有一個success的回調
success: function(json) { // asynchronously called on success, data is the json answer from the server var errorFieldId = json[0]; //var errorField = $($("#" + errorFieldId)[0]); var errorField = $("#"+ errorFieldId).eq(0); // make sure we found the element if (errorField.length == 1) { var status = json[1]; // read the optional msg from the server var msg = json[2]; if (!status) { // Houston we got a problem - display an red prompt options.ajaxValidCache[errorFieldId] = false; options.isError = true; // resolve the msg prompt if(msg) { if (options.allrules[msg]) { var txt = options.allrules[msg].alertText; if (txt) { msg = txt; } } }else msg = rule.alertText; if (options.showPrompts) methods._showPrompt(errorField, msg, "", true, options); } else { options.ajaxValidCache[errorFieldId] = true; // resolves the msg prompt if(msg) { //驗證之后需要調用的參數方法,這個參數存在與allrules中if (options.allrules[msg]) { var txt = options.allrules[msg].alertTextOk; if (txt) { msg = txt; } } } else msg = rule.alertTextOk; if (options.showPrompts) { // see if we should display a green prompt if (msg) methods._showPrompt(errorField, msg, "pass", true, options); else methods._closePrompt(errorField); } // If a submit form triggered this, we want to re-submit the form if (options.eventTrigger == "submit") field.closest("form").submit(); } }
這里回調函數中采用json[0],json[1],json[2]等方式取后台信息,個人覺得並不是很好,這樣導致很多時候,后台發來的數據需要根據這個API接口來封裝數據,有的時候會有點麻煩。感覺自定義回調比較好一點。這里我說明下這個回調里的幾個參數的含義errorFieldId:表示觸發ajax驗證的控件的id,這個id在放送請求的時候傳輸到后台,並再次由后台傳回前台,這個值需要有。status:舉個例子,如果你輸入一個用戶名,如果這個用戶名還沒有注冊,像前台傳輸一個status值,要非空,這樣告訴前台數據庫中沒有這個新建的用戶名。表示用戶名可以注冊。那第三個msg:如果你需要在ajax成功返回之后想觸發其他method里的方法,可以寫allrules里有的方法名即可,你也可以自己輸入字符串信息,那最后提示框中將使用你自定義的信息。



以上完成了插件的ajax驗證。上面的分析可能不是很全面,如果這個插件有新的功能我還沒有用到,希望大家提出來,一起分享分享。
內容不多,時間剛好,以上是我的一點讀碼體會,如有錯誤,請指出,大家共通學習。
