2012-07-29 102 views
1

我已經設法根據Niemeyer給出的答案創建了一個簡單的嚮導。這工作正常。我想添加驗證。我已經設法在字段Firstname上添加必需的驗證字。將此項留空將顯示錯誤。但是我不能成功的是: 驗證當前步驟中的模型,並根據是否存在錯誤啓用或禁用該模型。如果啓用或禁用下一個按鈕太困難,那沒問題。出現錯誤時,我也可以在沒有禁用按鈕的情況下生活。只要用戶無法在發生錯誤時繼續下一步。Knockout.js嚮導驗證每個步驟

。我的看法是這樣的:

//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; 
}; 

回答

6

這是一個棘手的問題,但我會爲您提供一些解決方案...

如果您只是想阻止Next按鈕繼續使用無效的模型狀態,那麼我發現的最簡單的解決方案是,從用於顯示驗證消息的<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依賴可觀察?」中,並且答案是調用該函數並不像它可能會發生的那樣工作。

因爲canGoNextdependentObservable,所以它的值是在它是變化成員的模型的任何時候計算的。但是,如果它的模型沒有改變,CanGoNext只是返回最後計算的值,即模型沒有改變,那麼爲什麼要重新計算它呢?

當僅檢查是否還有更多步驟時,這並不重要,但是當我嘗試在該函數中包含驗證時,這起到了作用。

爲什麼?那麼,例如,更改First Name會更新它所屬的NameModel,但在ViewModel中,self.nameModel未設置爲可觀察對象,因此儘管NameModel發生更改,但self.nameModel仍然相同。因此,ViewModel沒有改變,所以沒有理由重新計算canGoNext。最終的結果是,CanGoNext始終將該表單看作有效的,因爲它總是檢查self.nameModel,它永遠不會改變。

令人困惑,我知道,所以讓我再向你扔更多的代碼......

這裏是ViewModel的開始,我結束了:

function ViewModel(model) { 
    var self = this; 

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

    ... 

正如我提到的,模型需要可觀察到知道發生了什麼給他們。

現在更改goNextgoPrevious方法將工作而無需對這些模型觀察到,但要獲得你正在尋找的真正的實時驗證,當按鈕被禁用時,形式是無效的,使得模型可觀察是必要的。

儘管我最終保留了canGoNextcanGoPrevious函數,但我沒有將它們用於驗證。我會稍微解釋一下。

不過,首先,這是我加入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;     
}); 

[是啊,我知道...這個功能情侶視圖模型到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屬性。

canGoNextcanGoPrevious函數就像您提供的那樣 - 沒有變化。

這些綁定更改的一個結果是Previous按鈕在Name步驟中不可見,Next按鈕在Confirm步驟中不可見。

此外,在對所有數據屬性及其關聯表單域進行驗證時,從任何字段中刪除值將立即禁用「下一個」和/或「上一個」按鈕。

哎喲,這是很多解釋!

我可能已經離開的東西了,但這裏的鏈接,我以前得到這個工作的小提琴:http://jsfiddle.net/jimmym715/MK39r/

我敢肯定,有更多的工作要做,更多的障礙跨越大功告成之前與此,但希望這個答案和解釋幫助。

+2

我可能會做得非常類似,但可能會把「Step」對象的通用計算看起來是否有任何模型屬性無效(當前代碼只會執行頂級道具)。我會避免將視圖模型綁定到視圖上,方法是查找具有特定類的元素,並在模型中保存邏輯'$('。validationMessage:visible')'。 http://jsfiddle.net/rniemeyer/MK39r/23/ – 2012-07-29 20:36:38

+0

我喜歡這個解決方案!我曾想過在每個模型類中都使用'modelIsValid',但我認爲這同樣糟糕,甚至比我最終得到的還要糟糕。雖然我沒有想過在Step中定義它。我之前也沒有像JS那樣做過反射。偉大的東西......永遠感謝你的投入,瑞恩。 – jimmym715 2012-07-29 20:45:06

+0

我已經通過使用knockout.validation解決了它。在我看來,我認爲這比查看元素是否可見更清晰。我會將你的答覆標記爲答案。如果有人有興趣,我可以稍後發佈使用knockout.validation的解決方案。 – Mounhim 2012-07-30 19:56:39