每一步的 Knockout.js 向导验证 [英] Knockout.js wizard validation on each step

查看:22
本文介绍了每一步的 Knockout.js 向导验证的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have managed to create a simple wizard based on an answer given by Niemeyer. This works fine. I want to add validation. I have managed to add a required validion on the field Firstname. Leaving this empty displays an error. But what I could not succeed in is the following: Validate the model in the current step, and have the go next enabled or disabled based whether there are errors. If it is too difficult to enable or disable the next button, that is ok. I can also live without the button disabled when there are errors. As long as the user is prevented to proceed to the next step when there are errors.

.我的观点是这样的:

 //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 实现如下所示:

The knockout.js implementation looks like this:

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

并且我添加了一个扩展程序,用于字段名字中使用的所需验证:

And I have added an extender for the required validation as used in the field 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;
};

推荐答案

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

This was a tricky one, but I'll offer a couple of solutions for you...

如果您只是想阻止 Next 按钮继续处理无效的模型状态,那么我找到的最简单的解决方案是首先向每个 <span> 标签添加一个类用于显示验证消息:

If you simply want to prevent the Next button from proceeding with an invalid model state, then the easiest solution I found is to start by adding a class to each of the <span> tags that are used for displaying the validation messages:

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

(防止水平滚动的奇怪格式)

接下来,在 goNext 函数中,更改代码以包括检查是否有任何验证消息可见,如下所示:

Next, in the goNext function, change the code to include a check for whether or not any of the validation messages are visible, like this:

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

现在,您可能会问为什么不将该功能放在依赖 canGoNext 的 observable 中?",答案是调用该函数并没有像它可能的那样工作.

Now, you may be asking "why not put that functionality in the canGoNext dependent observable?", and the answer is that calling that function wasn't working like one might thing it would.

因为 canGoNext 是一个 dependentObservable,它的值会在它所属的模型发生变化时计算.

Because canGoNext is a dependentObservable, its value is computed any time the model that it's a member of changes.

但是,如果它的模型没有改变,canGoNext只是返回最后计算的值,即模型没有改变,那为什么要重新计算呢?

However, if its model hasn't changed, canGoNext simply returns the last calculated value, i.e. the model hasn't changed, so why recalculate it?

当仅检查是否还有更多步骤时,这并不重要,但是当我尝试在该函数中包含验证时,这发挥了作用.

This wasn't vital when only checking whether or not there were more steps remaining, but when I tried to include validation in that function, this came into play.

为什么?好吧,例如改变名字,更新它所属的NameModel,但是在ViewModel中,self.nameModel没有被设置为一个observable,所以尽管改变了NameModel,self.nameModel 还是一样.因此,ViewModel 没有改变,所以没有理由重新计算 canGoNext.最终结果是 canGoNext 总是认为表单有效,因为它总是检查 self.nameModel,它永远不会改变.

Why? Well, changing First Name, for example, updates the NameModel it belongs to, but in the ViewModel, self.nameModel is not set as an observable, so despite the change in the NameModel, self.nameModel is still the same. Thus, the ViewModel hasn't changed, so there's no reason to recompute canGoNext. The end result is that canGoNext always sees the form as valid because it's always checking self.nameModel, which never changes.

令人困惑,我知道,所以让我向您抛出更多代码......

Confusing, I know, so let me throw a bit more code at you...

这里是 ViewModel 的开头,最后是:

Here's the beginning of the ViewModel, I ended up with:

function ViewModel(model) {
    var self = this;

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

    ...

正如我提到的,模型需要是可观察的,才能知道它们发生了什么.

As I mentioned, the models need to be observable to know what's happening to them.

现在对 goNextgoPrevious 方法的更改无需使这些模型可观察即可工作,但要获得您正在寻找的真正实时验证,其中当表单无效时,按钮被禁用,使模型可观察是必要的.

Now the changes to the goNext and goPrevious methods will work without making those models observable, but to get the true real-time validation you're looking for, where the buttons are disabled when the form is invalid, making the models observable is necessary.

虽然我最终保留了 canGoNextcanGoPrevious 函数,但我没有将它们用于验证.我稍后会解释.

And while I ended up keeping the canGoNext and canGoPrevious functions, I didn't use them for validation. I'll explain that in a bit.

首先,这是我添加到 ViewModel 中用于验证的函数:

First, though, here's the function I added to ViewModel for validation:

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 中绑定这个函数:

And here's how I bound this function in the 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 属性.

Notice that I changed canGoNext and canGoPrevious so each is bound to its button's visible attribute, and I bound the modelIsValid function to the enable attribute.

canGoNextcanGoPrevious 函数与您提供的一样 - 那里没有变化.

The canGoNext and canGoPrevious functions are just as you provided them -- no changes there.

这些绑定更改的一个结果是,在 Name 步骤中看不到 Previous 按钮,在 Confirm 步骤中看不到 Next 按钮.

One result of these binding changes is that the Previous button is not visible on the Name step, and the Next button is not visible on the Confirm step.

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

In addition, when validation is in place on all of the data properties and their associated form fields, deleting a value from any field instantly disables the Next and/or Previous buttons.

哇,要解释的太多了!

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

I may have left something out, but here's the link to the fiddle I used to get this working: http://jsfiddle.net/jimmym715/MK39r/

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

I'm sure that there's more work to do and more hurdles to cross before you're done with this, but hopefully this answer and explanation helps.

这篇关于每一步的 Knockout.js 向导验证的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆