1

Niemeyerの回答に基づいて、簡単なウィザードを作成することができました。これは正常に機能します。検証を追加したい。フィールドFirstnameに必要な検証を追加することができました。これを空のままにすると、エラーが表示されます。しかし、私が成功できなかったのは次のとおりです。現在のステップでモデルを検証し、エラーがあるかどうかに基づいて次に進むを有効または無効にします。次のボタンを有効または無効にするのが難しすぎる場合は、問題ありません。エラーが発生した場合、ボタンを無効にせずに生活することもできます。エラーが発生したときにユーザーが次のステップに進むことができない限り。

。私の見解は次のようになります。

 //model is retrieved from server model
 <script type="text/javascript">
     var serverViewModel = @Html.Raw(Json.Encode(Model));
 </script>


<h2>Test with wizard using Knockout.js</h2>
  <div data-bind="template: { name: 'currentTmpl', data: currentStep }"></div> 
<hr/>

<button data-bind="click: goPrevious, enable: canGoPrevious">Previous</button>
<button data-bind="click: goNext, enable: canGoNext">Next</button>

<script id="currentTmpl" type="text/html">
    <h2 data-bind="text: name"></h2>
    <div data-bind="template: { name: getTemplate, data: model }"></div> 
</script>

<script id="nameTmpl" type="text/html">
    <fieldset>
        <legend>Naamgegevens</legend>
        <p data-bind="css: { error: FirstName.hasError }">
            @Html.LabelFor(model => model.FirstName)
            @Html.TextBoxFor(model => model.FirstName, new { data_bind = "value: FirstName, valueUpdate: 'afterkeydown'"})
            <span data-bind='visible: FirstName.hasError, text: FirstName.validationMessage'> </span>
        </p>
        @Html.LabelFor(model => model.LastName)
        @Html.TextBoxFor(model => model.LastName, new { data_bind = "value: LastName" })
    </fieldset>
</script>

<script id="addressTmpl" type="text/html">
    <fieldset>
        <legend>Adresgegevens</legend>
        @Html.LabelFor(model => model.Address)
        @Html.TextBoxFor(model => model.Address, new { data_bind = "value: Address" })
        @Html.LabelFor(model => model.PostalCode)
        @Html.TextBoxFor(model => model.PostalCode, new { data_bind = "value: PostalCode" })
        @Html.LabelFor(model => model.City)
        @Html.TextBoxFor(model => model.City, new { data_bind = "value: City" })
    </fieldset>
</script>

<script id="confirmTmpl" type="text/html">
        <fieldset>
        <legend>Naamgegevens</legend>
        @Html.LabelFor(model => model.FirstName)
        <b><span data-bind="text:NameModel.FirstName"></span></b>
        <br/>
        @Html.LabelFor(model => model.LastName)
        <b><span data-bind="text:NameModel.LastName"></span></b>
    </fieldset>
    <fieldset>
        <legend>Adresgegevens</legend>
        @Html.LabelFor(model => model.Address)
        <b><span data-bind="text:AddressModel.Address"></span></b>
        <br/>
        @Html.LabelFor(model => model.PostalCode)
        <b><span data-bind="text:AddressModel.PostalCode"></span></b>
        <br/>
        @Html.LabelFor(model => model.City)
        <b><span data-bind="text:AddressModel.City"></span></b>           
    </fieldset>
    <button data-bind="click: confirm">Confirm</button>
</script>

<script type='text/javascript'>
    $(function() {
        if (typeof(ViewModel) != "undefined") {
            ko.applyBindings(new ViewModel(serverViewModel));
        } else {
            alert("Wizard not defined!");
        }
    });
</script>

knockout.jsの実装は次のようになります。

function Step(id, name, template, model) {
    var self = this;
    self.id = id;
    self.name = ko.observable(name);
    self.template = template;
    self.model = ko.observable(model);

    self.getTemplate = function() {
        return self.template;
    };
}

function ViewModel(model) {
    var self = this;

    self.nameModel = new NameModel(model);
    self.addressModel = new AddressModel(model);

    self.stepModels = ko.observableArray([
            new Step(1, "Step1", "nameTmpl", self.nameModel),
            new Step(2, "Step2", "addressTmpl", self.addressModel),
            new Step(3, "Confirmation", "confirmTmpl", {NameModel: self.nameModel, AddressModel:self.addressModel})]);

    self.currentStep = ko.observable(self.stepModels()[0]);

    self.currentIndex = ko.dependentObservable(function() {
        return self.stepModels.indexOf(self.currentStep());
    });

    self.getTemplate = function(data) {
        return self.currentStep().template();
    };

    self.canGoNext = ko.dependentObservable(function () {
        return self.currentIndex() < self.stepModels().length - 1;
    });

    self.goNext = function() {
        if (self.canGoNext()) {
            self.currentStep(self.stepModels()[self.currentIndex() + 1]);
        }
    };

    self.canGoPrevious = ko.dependentObservable(function() {
        return self.currentIndex() > 0;
    });

    self.goPrevious = function() {
        if (self.canGoPrevious()) {
            self.currentStep(self.stepModels()[self.currentIndex() - 1]);
        }
    };
}

NameModel = function (model) {

    var self = this;

    //Observables
    self.FirstName = ko.observable(model.FirstName).extend({ required: "Please enter a first name" });;
    self.LastName = ko.observable(model.LastName);

    return self;
};

AddressModel = function(model) {

    var self = this;

    //Observables
    self.Address = ko.observable(model.Address);
    self.PostalCode = ko.observable(model.PostalCode);
    self.City = ko.observable(model.City);

    return self;
};

そして、フィールド名で使用される必要な検証用のエクステンダーを追加しました。

ko.extenders.required = function(target, overrideMessage) {
    //add some sub-observables to our observable    
    target.hasError = ko.observable();
    target.validationMessage = ko.observable();
    //define a function to do validation    

    function validate(newValue) {
        target.hasError(newValue ? false : true);
        target.validationMessage(newValue ? "" : overrideMessage || "This field is required");
    }

    //initial validation    
    validate(target());

    //validate whenever the value changes    
    target.subscribe(validate);
    //return the original observable    
    return target;
};
4

1 に答える 1

6

これはトリッキーなものでしたが、いくつかの解決策を提供します...

[次へ] ボタンが無効なモデル状態で続行しないようにするだけの場合、私が見つけた最も簡単な解決策は<span>、検証メッセージの表示に使用される各タグにクラスを追加することから始めることです。

<span class="validationMessage" 
      data-bind='visible: FirstName.hasError, text: FirstName.validationMessage'>

(水平スクロールを防ぐための奇妙な書式設定)

次に、goNext 関数で、コードを変更して、次のように検証メッセージが表示されるかどうかのチェックを含めます。

self.goNext = function() {
    if (
        (self.currentIndex() < self.stepModels().length - 1) 
        && 
        ($('.validationMessage:visible').length <= 0)
       ) 
    {
        self.currentStep(self.stepModels()[self.currentIndex() + 1]);
    }
};

さて、「なぜその機能をcanGoNext従属オブザーバブルに入れたらいいのだろうか?」と疑問に思うかもしれませんが、その答えは、その関数の呼び出しが、人が思っているように機能していなかったということです。

canGoNextは であるためdependentObservable、メンバーであるモデルが変更されるたびに、その値が計算されます。

ただし、モデルが変更されていない場合、canGoNext は最後に計算された値を返すだけです。つまり、モデルは変更されていないので、再計算する必要はありません。

これは、残りのステップがあるかどうかを確認するだけの場合は重要ではありませんでしたが、その関数に検証を含めようとしたときに、これが機能しました。

なんで?たとえば、First Name を変更すると、NameModelそれが属する が更新されますがViewModel、self.nameModel はオブザーバブルとして設定されていません。したがって、ViewModel は変更されていないため、canGoNext を再計算する理由はありません。最終結果として、canGoNext は常にフォームが有効であると認識します。

紛らわしいので、もう少しコードを投げさせてください...

これが の始まりですViewModel、私は次のようになりました:

function ViewModel(model) {
    var self = this;

    self.nameModel = ko.observable(new NameModel(model));
    self.addressModel = ko.observable(new AddressModel(model));

    ...

前述したように、モデルに何が起こっているかを知るには、モデルを観察可能にする必要があります。

goNextおよびメソッドへの変更は、goPreviousこれらのモデルを監視可能にせずに機能しますが、探している真のリアルタイム検証を取得するには、フォームが無効なときにボタンが無効になるため、モデルを監視可能にする必要があります。

canGoNext関数と関数を保持することになりcanGoPreviousましたが、検証には使用しませんでした。それについて少し説明します。

ただし、最初に、ViewModel検証のために追加した関数を次に示します。

self.modelIsValid = ko.computed(function() {
    var isOK = true;
    var theCurrentIndex = self.currentIndex();
    switch(theCurrentIndex)
    {
        case 0:
            isOK = (!self.nameModel().FirstName.hasError()
                    && !self.nameModel().LastName.hasError());
            break;
        case 1:
            isOK = (!self.addressModel().Address.hasError()
                    && !self.addressModel().PostalCode.hasError()
                    && !self.addressModel().City.hasError());
            break;
        default:
            break;
    };
    return isOK;                
});

[ええ、私は知っています... この関数は、ViewModel を NameModel クラスと AddressModel クラスに結合しますが、これらの各クラスのインスタンスを単に参照するだけではありませんが、今のところはそのままです。]

そして、この関数を HTML にバインドする方法は次のとおりです。

<button data-bind="click: goPrevious, 
                   visible: canGoPrevious, 
                   enable: modelIsValid">Previous</button>
<button data-bind="click: goNext, 
                   visible: canGoNext, 
                   enable: modelIsValid">Next</button>

変更canGoNextcanGoPreviousたため、それぞれがボタンのvisible属性にバインドされ、modelIsValid関数が属性にバインドされていることに注意してくださいenable

canGoNextおよび関数は、canGoPrevious提供されたとおりです。変更はありません。

これらのバインディングの変更の結果の 1 つは、[前へ] ボタンが [名前] ステップで表示されず、[次へ] ボタンが [確認] ステップで表示されないことです。

さらに、すべてのデータ プロパティとそれに関連付けられたフォーム フィールドで検証が行われている場合、任意のフィールドから値を削除すると、[次へ] または [前へ] ボタンが即座に無効になります。

うわー、それは説明することがたくさんあります!

私は何かを忘れたかもしれませんが、これを機能させるために使用したフィドルへのリンクは次のとおりです: http://jsfiddle.net/jimmym715/MK39r/

これを完了する前に、やらなければならない作業や乗り越えなければならないハードルがさらにあると確信していますが、この回答と説明が役立つことを願っています.

于 2012-07-29T18:36:44.030 に答える