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

我已经为 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;
};
4

1 回答 1

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依赖的可观察对象中?”,答案是调用该函数并没有像人们想象的那样工作。

因为canGoNext是 a dependentObservable,所以它的值会在它是其成员的模型发生变化的任何时候计算。

但是,如果它的模型没有改变,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));

    ...

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

goNext现在,对和方法的更改将在goPrevious不使这些模型可观察的情况下起作用,但要获得您正在寻找的真正实时验证,当表单无效时禁用按钮,使模型可观察是必要的。

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

不过,首先,这是我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属性。

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

这些绑定更改的一个结果是,Previous 按钮在 Name 步骤中不可见,而 Next 按钮在 Confirm 步骤中不可见。

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

哇,要解释的太多了!

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

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

于 2012-07-29T18:36:44.030 回答