Knockout.js向导验证每个步骤
我已经设法根据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;
};
而且我已经添加了必要的验证的扩展在该领域使用的名字:
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;
};
这是一个棘手的问题,但我会为您提供一些解决方案...
如果您只是想阻止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
是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
方法将工作而无需对这些模型观察到,但要获得你正在寻找的真正的实时验证,当按钮被禁用时,形式是无效的,使得模型可观察是必要的。
尽管我最终保留了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;
});
[是啊,我知道...这个功能情侣视图模型到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>
请注意,我更改了canGoNext
和canGoPrevious
,因此每个都绑定到其按钮的visible
属性,并且我将modelIsValid
函数绑定到enable
属性。
canGoNext
和canGoPrevious
函数就像您提供的那样 - 没有变化。
这些绑定更改的一个结果是Previous按钮在Name步骤中不可见,Next按钮在Confirm步骤中不可见。
此外,在对所有数据属性及其关联表单域进行验证时,从任何字段中删除值将立即禁用“下一个”和/或“上一个”按钮。
哎哟,这是很多解释!
我可能已经离开的东西了,但这里的链接,我以前得到这个工作的小提琴:http://jsfiddle.net/jimmym715/MK39r/
我敢肯定,有更多的工作要做,更多的障碍跨越大功告成之前与此,但希望这个答案和解释帮助。
我可能会做得非常类似,但可能会把“Step”对象的通用计算看起来是否有任何模型属性无效(当前代码只会执行顶级道具)。我会避免将视图模型绑定到视图上,方法是查找具有特定类的元素,并在模型中保存逻辑'$('。validationMessage:visible')'。 http://jsfiddle.net/rniemeyer/MK39r/23/ – 2012-07-29 20:36:38
我喜欢这个解决方案!我曾想过在每个模型类中都使用'modelIsValid',但我认为这同样糟糕,甚至比我最终得到的还要糟糕。虽然我没有想过在Step中定义它。我之前也没有像JS那样做过反射。伟大的东西......永远感谢你的投入,瑞恩。 – jimmym715 2012-07-29 20:45:06
我已经通过使用knockout.validation解决了它。在我看来,我认为这比查看元素是否可见更清晰。我会将你的答复标记为答案。如果有人有兴趣,我可以稍后发布使用knockout.validation的解决方案。 – Mounhim 2012-07-30 19:56:39