16

Eric Barnardのノックアウト検証ライブラリがオブザーバブルと統合され、グループ化が可能になり、カスタムバリデーターのプラグ可能性(オンザフライバリデーターを含む)を提供する方法が本当に気に入っています。より柔軟で使いやすいUXになる可能性のある場所がいくつかありますが、全体としてはかなり十分に文書化されています...非同期バリデーターに関しては、imoを除きます。

私はこれに数時間取り組んだ後、検索を行ってこれに着陸しました。私は元の作者と同じ問題/質問があると思いますが、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つの個別の検証グループがあります。ネストされたモデル#1には非同期バリデーターがなく、ネストされたモデル#2には同期(必須)と非同期の両方があります。asyncは、サーバー呼び出しを呼び出して入力を検証します。サーバーが応答すると、callback引数はko.validationユーザー入力が良いか悪いかを判断するために使用されます。示された行にブレークポイントを設定し、既知の無効な値を使用して検証をトリガーすると、ajaxsuccess関数によって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;
}

この関数は、監視可能なユーザー入力に接続されたサブスクリプションチェーン内にあるため、値が変更されたときに新しい値が検証されます。アルゴリズムは、入力に接続された各バリデーターをループし、バリデーターが非同期であるかどうかに応じて個別の関数を実行します。同期検証が失敗すると、ループが中断され、validateObservable関数全体が終了します。すべての同期バリデーターが合格すると、最後の3行が実行され、基本的にko.validationこの入力が有効であることを示します。ライブラリ内の__valid__関数は次のようになります。

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

これから取り除くべき2つのこと:__valid__は監視可能であり、関数が終了しtrueた後に設定されます。validateAsyncそれでは、見てみましょう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);
}

observableをtrueにko.validation.validateObservable設定して終了する前に、この関数の最初と最後の行のみが実行されることに注意することが重要です。__valid__この関数は、で宣言されcallBackた非同期関数に3番目のパラメーターとして渡されるものです。ただし、これが発生する前に、observableのサブスクライバーが呼び出され、非同期検証が開始されたことを通知します。サーバー呼び出しが完了すると、コールバックが呼び出されます(この場合、trueまたはfalseのいずれかを渡すだけです)。validatorMyViewModelisValidating

サーバー側の検証が失敗したときに、のブレークポイントMyViewModelが無限のピンポンループを引き起こしている理由は次のとおりです。上記の関数で、検証が失敗したときにobservableがfalseに設定されていることcallBackに注目してください。__valid__何が起こるかです:

  1. 無効なユーザー入力により、nestedModel2.prop2オブザーバブルが変更されます。
  2. ko.validation.validateObservable、この変更のサブスクリプションを介して通知されます。
  3. validateAsync関数が呼び出されます。
  4. カスタム非同期バリデーターが呼び出され$.ajax、サーバーに非同期呼び出しが送信されて終了します。
  5. ko.validation.validateObservable __valid__observableをに設定しtrueて終了します
  6. サーバーは無効な応答を返し、callBack(false)実行されます。
  7. callBack関数はに設定さ__valid__falseます。
  8. は、オブザーバブルへの変更が通知されます(からko.validation.validateObservableに変更されました)。これは、基本的に上記の手順2を繰り返します。__valid__callBacktruefalse
  9. 上記のステップ3、4、および5が繰り返されます。
  10. observableの値は変更されていないため、サーバーは別の無効な応答を返し、上記のステップ6、7、8、および9をトリガーします。
  11. 卓球の試合があります。

したがって、問題は、サブスクリプションハンドラーが、ユーザー入力値だけでなく、ネストされたオブザーバブルko.validation.validateObservableへの変更もリッスンしていることのようです。__valid__これはバグですか、それとも私は何か間違ったことをしていますか?

二次的な質問

ko.validation上記のソースから、非同期バリデーターを使用したユーザー入力値は、サーバーが検証している間は有効として扱われることがわかります。nestedModel2.isValid()このため、「真実」を求めることは信頼できません。代わりに、isValidatingフックを使用して非同期バリデーターへのサブスクリプションを作成し、値を通知した後にのみこれらの決定を行う必要があるようですfalse。これは仕様によるものですか?ライブラリの他の部分と比較すると、非同期バリデーターにはisValidatingサブスクライブする必要がなく、真実を伝えるために信頼できるため、これは最も直感に反しているように見えます。.isValid()これも設計によるものですか、それとも私はここでも何か間違ったことをしていますか?

4

2 に答える 2

15

したがって、私が尋ねた質問は、ko.validationで非同期バリデーターを使用する方法に関係していました。私の経験から学んだ2つの大きなポイントがあります。

  1. async 匿名または使い捨てのカスタムルールバリデーターを作成しないでください。代わりに、カスタムルールとして作成してください。そうしないと、私の質問で説明されている無限ループ/pingpingの一致になってしまいます。

  2. バリデーターを使用する場合は、すべてのバリデーターがfalseに変わるまでasync信頼しないでください。isValid()asyncisValidating subscriptions

複数の非同期バリデーターがある場合は、次のようなパターンを使用できます。

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() });
    }
};

同様の代替ソリューションを使用した別のリファレンスについても、これを確認できます。

非同期の「カスタムルール」の例を次に示します。

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のobservablesが元に戻りますfalse(つまり、非同期検証が完了し、入力が有効かどうかがわかります)。

上記は、検証が成功したときにサーバー側の検証エンドポイントがHTTP 200(OK)ステータスを返す場合に機能します。これ.doneは、と同等であるため、関数を実行し$.ajax successます。検証が失敗したときにサーバーがHTTP400(Bad Request)ステータスを返すと、.fail関数がトリガーされて実行されます。サーバーがカスタム検証メッセージを400で返す場合は、それを取得してxhr.responseText、デフォルトのメッセージを効果的にオーバーライドできyou suck because your input was wrong fix it or elseます。

于 2013-04-09T22:38:45.820 に答える
0

私は同じ問題を抱えていました。検証付きのネストされたオブザーバブルです。したがって、1つの魔法: self.errors = ko.validation.group(self.submissionAnswers, { deep: true, live: true }); 特別な追加のパラメータに注意してください:フィールドを含むオブジェクトlive: true

于 2019-06-05T11:56:39.077 に答える