Mou*_*him 1 validation wizard asp.net-mvc-3 knockout.js
我已经设法根据Niemeyer给出的答案创建了一个简单的向导.这很好用.我想添加验证.我已设法在字段名称上添加必需的验证.保留此空显示错误.但我无法成功的是:在当前步骤中验证模型,并根据是否存在错误启用或禁用下一步.如果启用或禁用下一个按钮太困难,那就没问题.当出现错误时,我也可以在没有禁用按钮的情况下生活.只要在出现错误时阻止用户进入下一步骤.
.我的观点如下:
//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>
Run Code Online (Sandbox Code Playgroud)
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;
};
Run Code Online (Sandbox Code Playgroud)
我已经为字段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;
};
Run Code Online (Sandbox Code Playgroud)
这是一个棘手的问题,但我会为你提供几个解决方案......
如果您只是想阻止Next按钮继续处理无效的模型状态,那么我找到的最简单的解决方案是首先为每个<span>用于显示验证消息的标记添加一个类:
<span class="validationMessage"
data-bind='visible: FirstName.hasError, text: FirstName.validationMessage'>
Run Code Online (Sandbox Code Playgroud)
(奇怪的格式以防止水平滚动)
接下来,在goNext函数中,更改代码以包括检查是否有任何验证消息可见,如下所示:
self.goNext = function() {
if (
(self.currentIndex() < self.stepModels().length - 1)
&&
($('.validationMessage:visible').length <= 0)
)
{
self.currentStep(self.stepModels()[self.currentIndex() + 1]);
}
};
Run Code Online (Sandbox Code Playgroud)
现在,您可能会问"为什么不将该功能放在canGoNext依赖的observable中?",答案是调用该函数并不像它那样工作.
因为canGoNext是a dependentObservable,它的值是在它成为其成员的模型的任何时候计算出来的.
但是,如果它的模型没有改变,canGoNext只返回最后计算的值,即模型没有改变,那么为什么要重新计算呢?
当仅检查是否还有更多步骤时,这并不重要,但当我尝试在该功能中包含验证时,这就开始起作用了.
为什么?好吧,例如,更改NameModel它所属的First Name会更新它,但是,在ViewModelself.nameModel中没有设置为observable,所以尽管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));
...
Run Code Online (Sandbox Code Playgroud)
正如我所提到的,模型需要被观察才能知道发生了什么.
现在对goNext和goPrevious方法的更改将起作用,而不会使这些模型可观察,但要获得您正在寻找的真实实时验证,当表单无效时禁用按钮,使模型可观察是必要的.
虽然我最终保留了canGoNext和canGoPrevious函数,但我没有使用它们进行验证.我稍后会解释一下.
首先,这是我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;
});
Run Code Online (Sandbox Code Playgroud)
[是的,我知道......这个函数将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>
Run Code Online (Sandbox Code Playgroud)
请注意,我已更改canGoNext,canGoPrevious因此每个都绑定到其按钮的visible属性,并将该modelIsValid函数绑定到该enable属性.
在canGoNext和canGoPrevious有没有变化-就像你为他们提供了功能.
这些绑定更改的一个结果是"上一步"按钮在"名称"步骤中不可见,"下一步"按钮在"确认"步骤中不可见.
此外,当对所有数据属性及其关联的表单字段进行验证时,从任何字段中删除值会立即禁用"下一个"和/或"上一个"按钮.
哇,这有很多要解释的!
我可能已经遗漏了一些东西,但这里是我以前用这个小提琴的链接:http://jsfiddle.net/jimmym715/MK39r/
在你完成这项工作之前,我确信还有更多的工作要做,还有更多的障碍可以跨越,但希望这个答案和解释有所帮助.