在ASP.NET MVC 4和KnockoutJS中使用Master-Detail表单时查看不更新

时间:2014-08-28 07:40:01

标签: asp.net-mvc-4 knockout.js

我正在尝试使用ASP.NET MVC 4和KnockoutJS为数据记录创建单页面编辑器。使用显示记录的表格和编辑单个记录的表单非常简单。

点击'编辑'编辑表单更新的记录,数据持久保存到数据库没有问题。此后有两个问题:

  1. 正在编辑的记录在保存后不会在表格中更新(即,观察者不会更新)
  2. 保存后,包含正在编辑的记录的控件不会清除。
  3. 我不知道如何解决(1)。对于(2),在Knockout完成后,有一些编写通用扩展方法或函数来清除任何形式的方法。我可以很容易地使用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 个答案:

答案 0 :(得分:2)

我会一次一个地回答你的观点,因为它们并没有真正相关。

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()。结果是一样的,但它不那么hacky。请注意,我认为上述解决方案仍然更可取,因为它会表现更好(需要更少的UI重排),并且当您稍后为此代码添加更多功能时它会更强大。

2)取决于你想要的东西。您希望编辑表单保持可见还是消失?您已经在使用with: selectedItem绑定,这使得消失行为变得非常简单:只需从selectItem(null)成功回调中调用save即可。如果您希望表单始终保持可见,并且只是清除字段,我想以下方法可行:

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
    }
}