jquery-validation.js在前端驗證中使用起來非常方便,提供的功能基本上能滿足大部分驗證需求,例如:1、內置了很多常用的驗證方法;2、可以自定義錯誤顯示信息;3、可以自定義錯誤顯示位置;4、可以自定義驗證方法;5、ajax提交驗證,等等
但是有時候,我們在做項目的時候總會遇到一些特殊需求,例如,在單個控件驗證結束后,根據驗證的成功與否,需要調用一些自己定義的方法,這個需求貌似該插件沒有提供(可能有只是我沒發現),沒辦法, 只能看源碼(這就是開源的好處啊),通過對源碼的分析,找到了一種可以給指定控件添加驗證回調函數的方法,雖然需要修改一部分源碼,但是絲毫不影響對其之前的使用,該方法可以批量添加多個控件的驗證回調函數,添加方式與添加自定義規則、自定義錯誤信息等類似,在閱讀源碼的過程中,還發現了如何控制控件驗證的事件觸發,以及如何解決與My97DatePicker日期插件的沖突等問題,所以建議大家多看源碼,有時候會有意外收獲哦。
因此,本文包括三個方面:
- 添加控件的驗證回調函數
- 控制控件驗證的事件觸發
- 解決與My97DatePicker日期插件的沖突問題
下面我們來一步步分析:
給指定控件添加自定義回調函數
首先,要添加回調函數必須找到該插件的驗證方法。其實在使用該插件的時候,從直觀的操作上我們就可以發現,控件驗證的觸發有多種方式,包括:控件焦點的失去,控件內容的改變(其實是keyup),以及點擊提交按鈕等,相信大家都知道,這些只是表象。經查看源碼,能觸發驗證的方法有很多,如:validate、form、checkForm和element等,但這依舊是表象,以我們程序員的嗅覺,真正的驗證函數肯定只有一個,經過深入勘察,前面每個方法都調用了check方法,在check方法中發現一句話:
var result = $.validator.methods[method].call(this, element.value.replace(/\r/g, ""), element, rule.parameters);
可見,這句代碼就是真正調用驗證邏輯的地方,因此,我們需要將自定義的驗證回調函數,放置在check方法中,修改后的代碼如下(黃色部分為添加的代碼):
check: function(element) { element = this.validationTargetFor(this.clean(element)); var rules = $(element).rules(); var dependencyMismatch = false; for (var method in rules) { var rule = { method: method, parameters: rules[method] }; try { var result = $.validator.methods[method].call(this, element.value.replace(/\r/g, ""), 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);
//驗證失敗回調函數 if ("invalidEventForElement" in this.settings) {
if (element.id in this.settings.validCallbackForElement) { this.settings.validCallbackForElement[element.id].fail(); } }
return false; } } catch (e) { this.settings.debug && window.console && console.log("exception occured when checking element " + element.id + ", check the '" + rule.method + "' method", e); throw e; } } if (dependencyMismatch) return; if (this.objectLength(rules)) this.successList.push(element);
//驗證成功回調函數
if ("invalidEventForElement" in this.settings) {
if (element.id in this.settings.validCallbackForElement) { this.settings.validCallbackForElement[element.id].success(); }
} return true; },
在這里,為了和插件的其他自定義屬性保持一致,我們定義了一個對象,結構如下:
validCallbackForElement:{ yourControlId1:{ success:function(){ //驗證成功回調 }, fail:function(){ //驗證失敗回調 } }, yourControlId2:{ success:function(){ //驗證成功回調 }, fail:function(){ //驗證失敗回調 } }, }
然后將其放入validate方法的參數中,示例如下(修改的時候將"yourControlId1"和"yourControlId2"更換成你自己的控件id):
$("#form1").validate({ rules: { yourControlId1: { required: true }, yourControlId2: { required: true, maxlength: 500 } }, //end rule messages: { yourControlId1: { required: "不能為空" }, yourControlId2: { required: "不能為空", maxlength: "內容過長" } }, errorPlacement: function(error, element) { if (element.context.name == "yourControlId1") { error.appendTo(document.getElementById("yourControlId1_error")); } else if (element.context.name == "yourControlId2") error.appendTo(document.getElementById("yourControlId2_error")); }, submitHandler: function(form) { ajaxSubmit(); }, validCallbackForElement: { yourControlId1: { success: function() { //控件yourControlId1的驗證成功回調 }, fail: function() { //控件yourControlId1的驗證失敗回調 } }, yourControlId2: { success: function() { //控件yourControlId2的驗證成功回調 }, fail: function() { //控件yourControlId2的驗證失敗回調 } } } }); //end validate function
在構造函數中,會將validCallbackForElement對象合並多this.settings對象中,因此,在調用的時候需要寫成:this.settings.validCallbackForElement[element.id].success();
因為我們不一定給所有的驗證控件都添加回調函數,因此,在調用的時候需要首先判斷該控件有沒有對應的毀掉函數,這樣,調用代碼就改為:
if ("invalidEventForElement" in this.settings) {
if (element.id in this.settings.validCallbackForElement) { this.settings.validCallbackForElement[element.id].fail(); }
}
到這里,如何給指定控件添加自定義驗證回調函數的問題就已經解決了。下面提供一個簡單的示例:
驗證觸發問題
在使用jquery-validation的時候發現,在調用驗證方法之前,控件的keyup和focusout事件是不能觸發驗證的,比如我們打開一個頁面,先不點擊提交按鈕,這樣就不對表單進行過驗證,這時候我們把光標放在input控件上,然后什么也不寫,在讓該input失去焦點,此時我們會發現,並沒有提示該input是必填項的錯誤信息,原因在於,jquery-validation插件在驗證表單的時候會將錯誤信息保存在errorMap這個變量中,errorMap的結構如下:
errorMap{ yourControlId1:"必填項", yourControlId2:"內容過長", ...... }
然后將errorMap合並到this.submited對象中,在focusout和keyup事件中,會判斷this.submited中是否有該控件的id,如果有才會執行驗證,代碼如下:
onfocusout: function(element, event) {
if (!that.checkable(element) && (element.name in that.submitted || !that.optional(element))) {
that.element(element);
}
},
onkeyup: function(element, event) {
if (element.name in this.submitted || element == this.lastElement) {
this.element(element);
}
},
因此,在執行表單驗證后才會激活控件的focusout和keyup驗證,如果你想一開始就能使focusout和keyup觸發驗證,可以在這里去掉紅色部分的代碼。
之前對這一部分的描述有問題,僅僅去掉紅色部分的代碼並不能達到驗證的目的,從源碼中可以看出,jquery-validation插件的作者對這里的控制還是比較嚴格的,不僅依賴submitted屬性,還要根據控件的值來做相應判斷,可能這款插件的意圖就是保證控件在沒有值並且沒有進行表單驗證之前不調用控件的驗證方法,這點通過onfocusout和onkeyup事件中的邏輯就能看出來,兩處的邏輯不太一樣。因此,如果想要在表單驗證前就能對空值的控件通過focusout和keyup觸發驗證方法,那么就需要修改很多地方,這顯然不符合該插件的編寫思路。
與My97DatePicker日期控件的沖突問題
相信My97DatePicker是很多人都用過的一個日期控件,該控件會與jquery-validation插件沖突,導致jquery-validation插件的focusout事件無法觸發驗證,可能是因為這兩個東西都注冊了focusout事件,所以導致了沖突,本來想看看My97DatePicker的源碼,打開之后發現是壓縮版的,好像還進行了混淆,所以如果在頁面中同時使用了這兩個東西的話,只能修改jquery-validation了,可以進行如下修改:
onfocusout: function(element, event) { //修改了此處代碼,否則會與My97DatePicker沖突,導致失去焦點的時候不會驗證 var that = this; setTimeout(function() { if (!that.checkable(element) && (element.name in that.submitted || !that.optional(element))) { that.element(element); } }, 100); },