15

我真的很喜歡Eric Barnard's knockout validation lib與observables集成,允許分組,&提供自定義驗證器可插件(包括動態驗證器)。有幾個地方可能更具用戶體驗靈活性/友好性,但總體來說它有相當充分的文檔記錄... except, imo, when it comes to async validatorsKnockout驗證異步驗證程序:這是一個錯誤還是我做錯了什麼?

今天我做了幾個小時的摔跤,然後再做搜索,並且landing on this。我認爲我有與原作者相同的問題/問題,但同意不清楚duxa的要求是什麼。我想提出更多關注的問題,所以我也在這裏問。

function MyViewModel() { 
    var self = this; 
    self.nestedModel1.prop1 = ko.observable().extend({ 
     required: { message: 'Model1 Prop1 is required.' }, 
     maxLength: { 
      params: 140, 
      message: '{0} characters max please.' 
     } 
    }); 
    self.nestedModel2.prop2 = ko.observable().extend({ 
     required: { message: 'Model2 Prop2 is required' }, 
     validation: { 
      async: true, 
      validator: function(val, opts, callback) { 
       $.ajax({         // BREAKPOINT #1 
        url: '/validate-remote', 
        type: 'POST', 
        data: { ...some data... } 
       }) 
       .success(function(response) { 
        if (response == true) callback(true); // BREAKPOINT #2 
        else callback(false); 
       }); 
      }, 
      message: 'Sorry, server says no :(' 
     } 
    }); 
} 

ko.validation.group(self.nestedModel1); 
ko.validation.group(self.nestedModel2); 

有關以上代碼的一些註釋:有兩個單獨的驗證組,每個嵌套模型一個。嵌套模型#1沒有異步驗證器,並且嵌套模型#2同時具有同步(必需)和異步。異步調用服務器調用來驗證輸入。當服務器響應時,參數callback用於告知ko.validation用戶輸入是好還是不好。 如果在指定的行上放置斷點並使用已知的無效值觸發驗證,則最終會出現無限循環,其中ajax success函數會導致validator函數再次被調用。我打開ko.validation來源,看看發生了什麼。

ko.validation.validateObservable = function(observable) { 
    // set up variables & check for conditions (omitted for brevity) 

    // loop over validators attached to the observable 
    for (; i < len; i++) { 
     if (rule['async'] || ctx['async']) { 
      //run async validation 
      validateAsync(); 
     } else { 
      //run normal sync validation 
      if (!validateSync(observable, rule, ctx)) { 
       return false; //break out of the loop 
      } 
     } 
    } 

    //finally if we got this far, make the observable valid again! 
    observable.error = null; 
    observable.__valid__(true); 
    return true; 
} 

此函數位於附加到用戶輸入observable的訂閱鏈中,以便在其值更改時驗證新值。該算法循環連接到輸入的每個驗證程序,並根據驗證程序是否是異步執行單獨的函數。如果同步驗證失敗,則循環被打破,整個validateObservable函數退出。如果所有同步驗證器均通過,則會執行最後3行,基本上告知ko.validation該輸入有效。庫中的__valid__功能如下:

//the true holder of whether the observable is valid or not 
observable.__valid__ = ko.observable(true); 

兩件事情,從這個帶走:__valid__是可觀察到的,並且它的validateAsync函數退出後設置爲true。現在,讓我們來看看validateAsync

function validateAsync(observable, rule, ctx) { 
    observable.isValidating(true); 

    var callBack = function (valObj) { 
     var isValid = false, 
      msg = ''; 

     if (!observable.__valid__()) { 
      // omitted for brevity, __valid__ is true in this scneario 
     } 

     //we were handed back a complex object 
     if (valObj['message']) { 
      isValid = valObj.isValid; 
      msg = valObj.message; 
     } else { 
      isValid = valObj; 
     } 

     if (!isValid) { 
      //not valid, so format the error message... 
      observable.error = ko.validation.formatMessage(...); 
      observable.__valid__(isValid); 
     } 

     // tell it that we're done 
     observable.isValidating(false); 
    }; 

    //fire the validator and hand it the callback 
    rule.validator(observable(), ctx.params || true, callBack); 
} 

需要注意的是ko.validation.validateObservable設置__valid__可觀察到真實,退出前僅此函數的第一個和最後一個行執行是非常重要的。 函數是作爲MyViewModel中聲明的異步validator函數的第3個參數傳遞的函數。然而在此之前,將調用isValidating observable的訂戶來通知異步驗證已開始。當服務器調用完成時,調用回調(在這種情況下只傳遞true或false)。

現在,這裏就是爲什麼在MyViewModel的斷點時,服務器端驗證失敗,也引起無限乒乓循環:在callBack功能上面,請注意驗證失敗時__valid__觀察到的設置爲false。以下是發生了什麼情況:

  1. 無效的用戶輸入更改了可觀察的nestedModel2.prop2
  2. 通過訂閱此更改通知ko.validation.validateObservable
  3. 調用validateAsync函數。
  4. 調用自定義異步驗證程序,該程序向服務器提交異步$.ajax調用並退出。
  5. ko.validation.validateObservable__valid__設置爲可觀察到true並且退出
  6. 服務器返回無效響應,執行callBack(false)
  7. callBack函數集__valid__false
  8. ko.validation.validateObservable通知__valid__可觀察到的變化(callBack將其從true更改爲false)這基本上重複上面的步驟2。
  9. 重複上述步驟3,4和5。
  10. 由於observable的值沒有改變,服務器返回另一個無效響應,觸發上面的步驟6,7,8,&9。
  11. 我們有自己的乒乓球比賽。

所以好像問題是ko.validation.validateObservable訂閱處理器是聽不只是用戶的輸入值的變化,也改變了其嵌套__valid__觀察到。這是一個錯誤,還是我做錯了什麼?

一種二次問題

可以從ko.validation源上面異步驗證該用戶輸入值看到在服務器驗證它被視爲有效。因此,致電nestedModel2.isValid()不能依靠「真相」。相反,它看起來像我們必須使用isValidating鉤子來創建異步驗證器的訂閱,並且只在他們通知值false後才做出這些決定。這是設計嗎?與庫的其餘部分相比,這似乎是最直觀的,因爲異步驗證程序沒有isValidating訂閱,而可以依賴.isValid()說實話。這是否也是設計,還是我在這裏做錯了?

+0

你已經爲這個問題付出了很多努力,很抱歉看到你沒有任何迴應。我只是新的KO驗證,所以我不能真正幫助你。 – GONeale

+3

這個問題有一些問題。被問到的實際問題並不清楚,並被埋在外部鏈接中。在意識到這一點之前,我必須閱讀幾段文字。這個問題有太多的背景;我看不到樹林。你似乎在問多個問題。 建議:在JSFiddle中創建問題的最簡單的代碼,在您的問題中鏈接到它,並在第一段中提出一個簡潔的問題。做到這一點,你會更有可能得到一些答案。 –

+0

你可以更新或可能回答你的問題?我看到你已經想出了這個網址的解決方案... https://github.com/ericmbarnard/Knockout-Validation/issues/145。你的答案會幫助很多人,因爲沒有太多有關異步驗證的信息。 – bflemi3

回答

14

所以我問的問題確實與如何在ko.validation中使用異步驗證器有關。有2個,我已經從我的經驗教訓的大外賣:

  1. 不要創建asyncAnonymous or Single-Use Custom Rule validators。相反,將它們創建爲Custom Rules。否則,你將最終得到我的問題中描述的無限循環/ ping ping匹配。

  2. 如果使用async驗證,不信任isValid(),直到所有async驗證isValidatingsubscriptions更改爲false。

如果有多個異步驗證,您可以使用類似以下的模式:

var viewModel = { 
    var self = this; 
    self.prop1 = ko.observable().extend({validateProp1Async: self}); 
    self.prop2 = ko.observable().extend({validateProp2Async: self}); 
    self.propN = ko.observable(); 
    self.isValidating = ko.computed(function() { 
     return self.prop1.isValidating() || self.prop2.isValidating(); 
    }); 
    self.saveData = function(arg1, arg2, argN) { 

     if (self.isValidating()) { 
      setTimeout(function() { 
       self.saveData(arg1, arg2, argN); 
      }, 50); 
      return false; 
     } 

     if (!self.isValid()) { 
      self.errors.showAllMessages(); 
      return false; 
     } 

     // data is now trusted to be valid 
     $.post('/something', 'data', function() { doWhatever() }); 
    } 
}; 

您也可以see this for another reference with similar alternate solutions

這裏是一個異步「自定義規則」的一個例子:

var validateProp1Async = { 
    async: true, 
    message: 'you suck because your input was wrong fix it or else', 
    validator: function(val, otherVal, callback) { 
     // val will be the value of the viewmodel's prop1() observable 
     // otherVal will be the viewmodel itself, since that was passed in 
     //  via the .extend call 
     // callback is what you need to tell ko.validation about the result 
     $.ajax({ 
      url: '/path/to/validation/endpoint/on/server', 
      type: 'POST', // or whatever http method the server endpoint needs 
      data: { prop1: val, otherProp: otherVal.propN() } // args to send server 
     }) 
     .done(function(response, statusText, xhr) { 
      callback(true); // tell ko.validation that this value is valid 
     }) 
     .fail(function(xhr, statusText, errorThrown) { 
      callback(false); // tell ko.validation that his value is NOT valid 
      // the above will use the default message. You can pass in a custom 
      // validation message like so: 
      // callback({ isValid: false, message: xhr.responseText }); 
     }); 
    } 
}; 

基本上,您使用callback ARG到validator功能告訴ko.validation驗證是否成功。這個調用就是觸發isValidating可驗證屬性observables上的可觀察性變回false(意思是說,異步驗證已完成,現在知道輸入是否有效)。

如果您的服務器端驗證端點在驗證成功時返回HTTP 200(OK)狀態,上述方法將工作。這將導致.done函數執行,因爲它等效於$.ajaxsuccess。如果您的服務器在驗證失敗時返回HTTP 400(錯誤請求)狀態,則會觸發.fail函數執行。如果您的服務器使用400返回自定義驗證消息,則可以從xhr.responseText中獲得該消息以有效覆蓋默認的you suck because your input was wrong fix it or else消息。

+0

謝謝,這幫了我很多:) – bflemi3

+0

你還介意給你一個驗證規則'validateProp1Async'的例子嗎? – bflemi3

+0

在knockout-validation wiki(https://github.com/ericmbarnard/Knockout-Validation/wiki/Async-Rules)中的異步規則頁面上,顯示的ajax示例令我困惑。 '驗證器'不返回真或假。如果該規則不返回true或false,規則如何驗證該值?這就是爲什麼我要求查看您的驗證規則。對於所有的問題抱歉 - 在文檔和Eric Barnard等方面沒有多少,你是我見過的另外一個人,他似乎完全掌握了異步工作方式。 – bflemi3

相關問題