我正试图使用ASP.NET MVC 4和KnockoutJS为数据记录创建一个单页编辑器。它非常简单,有一个显示记录的表和一个编辑单个记录的表单。
当单击"编辑"编辑记录时,表单会更新,数据会保留到数据库中,而不会出现问题。之后有两个问题:
- 保存后,正在编辑的记录不会在表中更新(即,可观测值不会更新)
- 保存后,包含正在编辑的记录的控件不会清除
我不知道如何解决(1)。对于(2),是否有某种方法可以在Knockout完成后编写一个通用的扩展方法或函数来清除ANY表单。我可以很容易地使用JQuery来完成这项工作,但我可能缺少Knockout已经可以完成的功能。
页面代码如下:
@model IEnumerable<SiteDto>
@{
ViewBag.Title = "Index";
}
<h2>Sites</h2>
<table>
<caption>Sites</caption>
<thead>
<tr>
<th>Name</th>
<th>Link</th>
<th>Url</th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: sites">
<tr>
<td><span data-bind="text: id"></span></td>
<td><span data-bind="text: name"></span></td>
<td><span data-bind="text: url"></span></td>
<td><button data-bind="click: $parent.selectItem">Edit</button></td>
</tr>
</tbody>
</table>
<div data-bind="with: selectedItem">
<table>
<caption data-bind="text: name"></caption>
<tbody>
<tr>
<td><input data-bind="value: id" /></td>
</tr>
<tr>
<td><input data-bind="value: url" /></td>
</tr>
<tr>
<td><input data-bind="value: name" /></td>
</tr>
</tbody>
</table>
<button data-bind="click: save">Save</button>
</div>
<script type="text/javascript">
function viewModel() {
var sites = ko.observableArray(@Html.Raw(Model.ToJson()));
var selectedItem = ko.observable();
selectItem = function (s) {
selectedItem(s);
};
save = function () {
alert(ko.toJSON(selectedItem));
$.ajax({
url: "/Home/Save",
type: "POST",
data: ko.toJSON(selectedItem),
contentType: "application/json",
dataType: "json",
success: function(result) {
alert(result);
},
error: function() {
alert("fail");
}
});
};
return {
sites: sites,
selectedItem: selectedItem,
selectItem: selectItem,
save: save
}
}
ko.applyBindings(viewModel);
</script>
我会一次回答一个问题,因为它们并没有真正的相关性。
1) 这里的问题是,您采用ASP.NET MVC模型,并将其放入observableArray中。问题是,如果项目被添加、删除或交换,observableArray将更新UI,但它不会通知UI单个项目的更改。因此,即使您确实正确地编辑了行,UI也永远不会知道。理想的解决方案不是简单地将MVC模型注入observableArray,而是将模型映射到一个数据结构,在该数据结构中,项的可编辑属性(id、url、name)是可观察的。未测试的演示代码:
var rawSites = @Html.Raw(Model.ToJson()),
sites = ko.observableArray(rawSites.map(function (rawSite) {
return {
id: ko.observable(rawSite.id),
url: ko.observable(rawSite.url),
name: ko.observable(rawSite.name)
};
}));
编辑:我最初的回答提出了第二种方法,通过从observableArray中删除编辑后的项并重新添加它来"入侵"UI更新。@Tomalak在评论中提出了一个更好的建议:在该项上使用valueHasMutated()
。结果是一样的,但没有那么粗糙。请注意,在我看来,上面的解决方案仍然更可取,因为它会执行得更好(需要更少的UI回流),并且当您稍后向代码添加更多功能时,它会更健壮。
2) 这在一定程度上取决于你想要什么。您希望编辑表单保持可见还是消失?您已经使用了with: selectedItem
绑定,这使得消失行为非常简单:只需从save
成功回调中调用selectItem(null)
。如果您希望表单始终可见,并且只清除字段,我想以下方法会起作用:
function viewModel() {
var sites = ko.observableArray(@Html.Raw(Model.ToJson()));
var originalItem = null;
var selectedItem = {
id: ko.observable(),
url: ko.observable(),
name: ko.observable()
};
var selectItem = function (s) {
// This function now copies the properties instead of using the item itself
selectedItem.id(ko.unwrap(s.id));
selectedItem.url(ko.unwrap(s.url));
selectedItem.name(ko.unwrap(s.name));
// Get a reference to s so we can update it when we are done editing
originalItem = s;
};
var resetSelectedItem = function () {
// Clear the form and reset the reference we held earlier
selectItem({
id: null,
url: null,
name: null
});
originalItem = null;
};
save = function () {
alert(ko.toJSON(selectedItem));
$.ajax({
url: "/Home/Save",
type: "POST",
data: ko.toJSON(selectedItem),
contentType: "application/json",
dataType: "json",
success: function(result) {
alert(result);
// Done editing: update the item we were editing
originalItem.id(selectedItem.id());
originalItem.url(selectedItem.url());
originalItem.name(selectedItem.name());
// Clear the form
resetSelectedItem();
},
error: function() {
alert("fail");
// Clear the form
resetSelectedItem();
}
});
};
return {
sites: sites,
selectedItem: selectedItem,
selectItem: selectItem,
save: save
}
}