【发布时间】:2017-09-14 10:09:01
【问题描述】:
我的淘汰赛应用中有一个下拉列表,标记如下:
查看:
<select data-bind="options: $root.counties, optionsText: 'name', value: county, optionsCaption: '@PlanStrings.ChooseFromDropdownMessage'"></select>
我希望验证这一点以确保选择了一个选项。 它在浏览器中呈现如下:
<select data-bind="options: $root.counties, optionsText: 'name', value: county, optionsCaption: 'Choose...'">
<option value="">Choose...</option>
<option value="">Carlow</option>
<option value="">Cavan</option>
<option value="">Clare</option>
<option value="">Cork</option>
<option value="">Donegal</option>
<option value="">Dublin</option>
<option value="">Galway</option>
<option value="">Kerry</option>
<option value="">Kildare</option>
<option value="">Kilkenny</option>
<option value="">Laois</option>
<option value="">Leitrim</option>
<option value="">Limerick</option>
<option value="">Longford</option>
<option value="">Louth</option>
<option value="">Mayo</option>
<option value="">Meath</option>
<option value="">Monaghan</option>
<option value="">Offaly</option>
<option value="">Roscommon</option>
<option value="">Sligo</option>
<option value="">Tipperary</option>
<option value="">Waterford</option>
<option value="">Westmeath</option>
<option value="">Wexford</option>
<option value="">Wicklow</option>
</select>
county 是一个计算出来的 observable,代码如下:
/// <summary>Constructor function for site info view model</summary>
function SiteModel(data, parent) {
var self = this;
// some code etc
self.county = ko.computed({
read: function () {
return ko.utils.arrayFirst(
parent.counties(),
i => i.countyId() === self.countyId()
);
},
write: function (value) {
self.countyId(value === undefined ?
null : value.countyId());
}
});
}
在整个 ViewModel() 函数中调用 SiteModel。
我已经在同一页面上的一个简单输入文本字段(非计算)上实现了 ko 验证,因此设置 ko 验证不是更基本的问题。我只是在做错事。验证这个计算出来的 observable/dropdown。
我已经尝试了以下所有方法:
self.countyId = ko.observable().extend({ required: { message: "You must select a county." } });
self.county.extend({ required: { message: "You must select a county." } });
self.countyId.extend({ required: { message: "You must select a county." } });
self.county = ko.computed({
read: function () {
return ko.utils.arrayFirst(
parent.counties(),
i => i.countyId() === self.countyId()
);
},
write: function (value) {
self.countyId(value === undefined ?
null : value.countyId());
}
}).extend({ required: { message: "You must select a county." } });
这些似乎都不起作用。 新来的 ko 所以对此一头雾水。 有关如何验证此下拉/计算 observable 的任何建议?
我可能会补充一点,此下拉菜单的所有其他方面都可以正常工作,因为它在保存页面时会正确读取值,并且如果模型包含值,则使用模型中的值作为选定值呈现。
##EDIT## 包括完整的 View 模型代码以确保完整性:
function ($, ko, validation, mapping, formatting, strings, global, base) {
/// <summary>Constructor function for top-level view model</summary>
function ViewModel(data) {
var self = this;
var map = {
ignore: ["RegistrationDate"],
sites: {
create: function (options) {
return new SiteModel(options.data, self);
}
}
};
mapping.fromJS(data, map, self);
self.industrySector = ko.computed({
read: function () {
return ko.utils.arrayFirst(
self.industrySectors(),
i => i.industrySectorId() === self.industrySectorId()
);
},
write: function (value) {
self.industrySectorId(value === undefined ?
null : value.industrySectorId());
}
});
self.isEditingNumberOfEmployees = ko.observable(false);
self.editNumberOfEmployees = () => self.isEditingNumberOfEmployees(true);
self.numberOfEmployeesFormatted =
ko.computed(() => {
var value = ko.unwrap(self.numberOfEmployees);
return value === null ?
strings.nullPlaceholder :
value;
});
self.isEditingTurnover = ko.observable(false);
self.editTurnover = () => self.isEditingTurnover(true);
self.turnoverFormatted =
ko.computed(() => {
var value = ko.unwrap(self.turnover);
if (value !== null)
{
var val_string = value.toString();
var value_parsed = parseFloat(val_string.replace(/[^\d\.]/g, ''));
}
return value === null ?
strings.nullPlaceholder :
formatting.formatDecimal(value_parsed);
});
self.isEditingYearEstablished = ko.observable(false);
self.editYearEstablished = () => self.isEditingYearEstablished(true);
self.yearEstablishedFormatted =
ko.computed(() => {
var value = ko.unwrap(self.yearEstablished);
return value === null ?
strings.nullPlaceholder :
value;
});
self.shouldShowLogoMessage = ko.observable(true);
var companyLogoId = data.logoFileId;
if (companyLogoId > 0)
{
self.shouldShowLogoMessage(false);
}
self.uploadLogo = function (data, event) {
var inputId = $(event.target).data("inputId");
$("#" + inputId).trigger("click");
};
self.liveSites = ko.computed(function () {
return ko.utils.arrayFilter(
self.sites(),
site => ko.unwrap(site.state) !== "Deleted"
);
});
self.addSite = function () {
var newSite = new SiteModel(
{
name: null,
address1: null,
address2: null,
address3: null,
countyId: null,
keyActivities: null,
state: "Added"
},
self
);
self.sites.push(newSite);
self.setTab(newSite.position());
}
self.setTab = function (tabIndex) {
// FIXME Investigate replacing with KO microtask when upgraded
// to KO 3.4
window.setTimeout(function () {
$("#sites_tab").tabs("option", "active", tabIndex);
}, 10);
};
self.siteCount = ko.computed(() => self.liveSites().length);
self.dirtyFlag = new ko.dirtyFlag(self, false);
}
var knockoutValidationSettings = {
insertMessages: true,
messagesOnModified: true,
};
ko.validation.init(knockoutValidationSettings, true);
ViewModel.prototype = base;
/// <summary>Constructor function for site info view model</summary>
function SiteModel(data, parent) {
var self = this;
mapping.fromJS(data, {}, self);
self.position = ko.computed(function () {
return parent.sites().indexOf(self);
});
self.hasData = function () {
return (self.name() !== "" ||
self.address1() !== "" ||
self.address2() !== "");
};
self.displayName = ko.computed({
read: function () {
if (self.name() === "" || self.name() === null) {
return strings.introductionSiteAutoName + " " + (parent.liveSites ?
parent.liveSites().indexOf(self) + 1 :
parent.sites().indexOf(self) + 1).toString();
}
return self.name();
},
write: function (value) {
self.name(value);
}
});
self.anchorRef = ko.computed(function () {
return "#tabs-" + (self.position() + 1).toString();
});
self.idRef = ko.computed(function () {
return "tabs-" + (self.position() + 1).toString();
});
self.stateModified = ko.computed(function () {
self.name();
self.address1();
self.address2();
if (self.state() === "Unchanged") {
self.state("Modified");
}
});
self.deleteSite = function () {
var pos = self.position();
// FIXME: Replace with proper dialog box
if (window.confirm(strings.introductionConfirmDeleteSiteMessage)) {
if (self.state() === "Added") {
// Site was never saved to DB so just remove it
parent.sites().splice(pos, 1);
}
self.state("Deleted");
if (pos > 0) pos--;
parent.setTab(pos);
}
}
self.county = ko.computed({
read: function () {
return ko.utils.arrayFirst(
parent.counties(),
i => i.countyId() === self.countyId()
);
},
write: function (value) {
self.countyId(value === undefined ?
null : value.countyId());
}
}).extend({ required: true });
self.countyHidden = ko.computed({
read: function () {
return ko.utils.arrayFirst(
parent.counties(),
i => i.countyId() === self.countyId()
);
},
write: function (value) {
self.countyId(value === undefined ?
null : value.countyId());
}
}).extend({ required: true });
self.name.extend({ required: { message: "You must enter a name." } });
}
return {
ViewModel: ViewModel,
};
}
【问题讨论】:
-
@RoyJ ive 在视图中的 select data-bind 中应用了该选项,现在它返回以下错误: Uncaught TypeError: parent.counties is not a function 这发生在 ko 内的读取中。计算函数。这似乎仅在我尝试将 .extend 应用于此计算时才会发生。
-
看起来好像没有在顶层定义
counties,这就是parent所指向的,对吧? -
@RoyJ parent 从 ViewModel 函数中引用 self。我已经对其进行了调试,并且 parent.counties() 似乎确实包含数据。在 ViewModel(data) 中有一个名为counties 的数组,其中包含用于填充下拉选项的数据。从某种意义上说,下拉菜单完美地工作,它填充了所有必需的选项,并且在页面加载时选择了记录的选项。我想要的是它识别何时选择“选择...”并指示必须选择一个选项。看起来应该很简单,但它让我发疯!
-
仍然无法通过选择下拉菜单解决此问题,因此我添加了一个隐藏字段并将其绑定到计算的县对象中使用的县 ID 值。因此,当下拉列表更改时,隐藏字段的值也会随之更改,并且该字段上有一个必需的扩展器。因此,从所有意图和目的来看,验证看起来和行为都像是属于下拉列表。它是一个黑客,但让我解决了这个问题。感谢您的帮助@RoyJ :)
标签: knockout.js knockout-validation