【问题标题】:Knockout.js wizard validation on each step每个步骤的 Knockout.js 向导验证
【发布时间】:2012-07-27 09:36:01
【问题描述】:

我已经设法根据 Niemeyer 给出的答案创建了一个简单的向导。这工作正常。我想添加验证。我设法在 Firstname 字段上添加了必需的验证。将此留空将显示错误。但我无法成功的是以下内容: 在当前步骤中验证模型,并根据是否有错误启用或禁用 go next。如果启用或禁用下一个按钮太难,那没关系。当出现错误时,我也可以在没有禁用按钮的情况下生活。只要在出现错误时阻止用户进行下一步即可。

。我的观点是这样的:

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

【问题讨论】:

    标签: asp.net-mvc-3 validation knockout.js wizard


    【解决方案1】:

    这是一个棘手的问题,但我会为您提供几个解决方案...

    如果您只是想阻止“下一步”按钮继续处理无效的模型状态,那么我发现的最简单的解决方案是首先向每个用于显示验证消息的 &lt;span&gt; 标记添加一个类:

    <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 的 observable 中?”,答案是调用该函数并不能正常工作。

    因为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;                
    });
    

    [是的,我知道...这个函数将 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 属性。

    canGoNextcanGoPrevious 函数与您提供的一样 - 没有更改。

    这些绑定更改的一个结果是“上一个”按钮在“名称”步骤中不可见,而“下一个”按钮在“确认”步骤中不可见。

    此外,当对所有数据属性及其关联的表单字段进行验证时,从任何字段中删除值会立即禁用“下一个”和/或“上一个”按钮。

    哇,要解释的东西太多了!

    我可能遗漏了一些东西,但这里是我用来让它工作的小提琴的链接:http://jsfiddle.net/jimmym715/MK39r/

    我确信在您完成此操作之前还有更多工作要做,还有更多障碍要跨越,但希望这个答案和解释会有所帮助。

    【讨论】:

    • 我可能会非常相似地做它,但可能会在“Step”对象上放置一个通用计算,以查看是否有任何模型属性无效(当前代码只会做顶级道具) .我会通过查找具有特定类的元素$('.validationMessage:visible') 来避免将视图模型与视图绑定,并将逻辑保留在模型中。 jsfiddle.net/rniemeyer/MK39r/23
    • 我非常喜欢这个解决方案!我曾考虑在每个模型类中使用modelIsValid,但我认为这与我最终得到的结果一样糟糕或更糟。不过,我没有想过在 Step 中定义它。我以前也没有在 JS 中进行过这样的反射。很棒的东西...永远感谢您的意见,Ryan。
    • 我已经通过使用 knockout.validation 解决了这个问题。在我看来,我认为这比查找元素是否可见更干净。我会将您的回复标记为答案。如果有人感兴趣,我可以稍后发布使用 knockout.validation 的解决方案。
    • 我在这里尝试小提琴jsfiddle.net/jimmym715/MK39r,但显示的是第一页(也不是任何其他页面)。有任何想法吗? @Mounhim 我也很想看到你的小提琴需要解决同样的问题:)
    • 好的,所以我看到淘汰赛不是参考我在这里更新了jsfiddle.net/KQYRz。不过仍然对@Mounhim 的验证解决方案感兴趣
    猜你喜欢
    • 2018-11-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-05
    • 1970-01-01
    • 1970-01-01
    • 2023-03-14
    相关资源
    最近更新 更多