在ASP.NET MVC 4和KnockoutJS中使用Master Detail表单时,视图未更新



我正试图使用ASP.NET MVC 4和KnockoutJS为数据记录创建一个单页编辑器。它非常简单,有一个显示记录的表和一个编辑单个记录的表单。

当单击"编辑"编辑记录时,表单会更新,数据会保留到数据库中,而不会出现问题。之后有两个问题:

  1. 保存后,正在编辑的记录不会在表中更新(即,可观测值不会更新)
  2. 保存后,包含正在编辑的记录的控件不会清除

我不知道如何解决(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
    }
}

最新更新