2012-07-29 4 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; 
}; 

그리고 필드 FIRSTNAME에서 사용 된 내가 필요한 검증하기위한 확장을 추가했습니다 :

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

이것은 까다로운 문제 였지만 두 가지 해결책을 제시해 드리겠습니다 ...

단순히 다음 버튼이 잘못된 모델 상태로 진행되는 것을 막으려면 가장 쉬운 해결책은 유효성 검사 메시지를 표시하는 데 사용되는 각 <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을 검사하기 때문에 양식을 유효한 것으로 간주합니다.

혼란 스럽습니다. 알고 있습니다. 코드를 좀 더 알려 드리겠습니다.

function ViewModel(model) { 
    var self = this; 

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

    ... 

내가 언급 한 바와 같이이 모델이 그들에게 무슨 일이 일어나고 있는지 알고 관찰 할 필요가 :

가 여기에 ViewModel의 시작에, 나는 함께했다.

이제 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 기능은 사용자가 입력 한 기능과 동일합니다. 변경 사항은 없습니다.

이러한 바인딩 변경 결과 중 하나는 이름 단계에서 이전 단추가 보이지 않고 확인 단계에서 다음 단추가 표시되지 않는다는 것입니다.

또한 모든 데이터 속성과 관련 양식 필드에서 유효성 검사가 수행되면 모든 필드에서 값을 삭제하면 즉시 다음 및/또는 이전 버튼이 비활성화됩니다.

휴, 설명 할 것이 많습니다!

나는 무언가를 떠난 수 있지만, 여기에 내가이 작업을 진행하는 데 사용되는 바이올린에 대한 링크 : 당신이 완료되기 전에 http://jsfiddle.net/jimmym715/MK39r/

내가 할 수있는 더 많은 작업과 교차 더 많은 장애물이 있다는 확신이 이것으로, 그러나 잘하면이 대답과 설명이 도움이됩니다.

+2

나는 꽤 유사 할 것입니다. 그러나 모델 속성 중 어떤 것이 유효하지 않은지 (현재 코드는 최상위 소품 만 수행 할 것입니다) 보이는 "단계"객체에 대해 계산 된 일반을 넣을 것입니다. 보기 모델을 뷰에 묶는 것은'$ ('. validationMessage : visible')'클래스의 특정 클래스를 가진 요소를 찾고 모델에 로직을 유지하는 것을 피할 것이다. http://jsfiddle.net/rniemeyer/MK39r/23/ –

+0

나는 그 해결책을 많이 좋아한다! 각 모델 클래스에서'modelIsValid'를 사용하는 것에 대해 생각해 보았습니다. 그러나 나는 그것이 결국 나쁘게 나쁘게 나쁘다고 생각했습니다. 그래도 Step에서 정의 할 생각은 없었습니다. 전에 JS에서 그런 식으로 반사를하지 않았습니다. 좋은 점 ... 항상 귀하의 의견에 감사드립니다, Ryan. – jimmym715

+0

knockout.validation을 사용하여 해결했습니다.제 생각에는 요소가 보이는지를 조사하는 것보다 더 깔끔하다고 생각합니다. 답장으로 답장을 표시하겠습니다. 관심있는 사람이 있다면 나중에 knockout.validation이 사용 된 솔루션을 게시 할 수 있습니다. – Mounhim

관련 문제