敲除计算数组未正确更新

Knockout computed arrays not updating correctly

我很难根据我的视图模型更新我的视图。总结一下我在做什么:我有一组项目 (AllCredentials)。项目的属性之一是 "IsSelected" 值(真或假)。当某个项目的值为 false(创建 AllCredentials 数组时的默认值)时,该项目将出现在 "UnselectedCredentials" 列表中。当双击此列表中的项目时,其 "IsSelected" 的值将被切换,从而使其出现在另一个 "SelectedCredentials" 列表中。

当我测试它时,"IsSelected" 值在双击时正确切换,但计算数组(其中 2 个,每个对应 2 个列表)没有得到 added/removed 相应地(如 "UnselectedCredentials" 列表中的项目被双击时,其 "IsSelected" 的值应从 false 切换为 true 从而将其从该列表中删除并将其添加到 "SelectedCredentials" 列表).

这是我的代码:

视图模型:

var TestNWJS = TestNWJS || {};

TestNWJS.QualificationList = (function () {

//private functions

function FindUnselectedCredentials() { //function to populate UnselectedCredentials list

    var filtering = ko.utils.arrayFilter(TestNWJS.QualificationList.ViewModel.AllCredentials(), function (item) {
        return item.IsSelected === false;
    });

    return filtering;
}

function FindSelectedCredentials() { //function to populate SelectedCredentials list

    var filtering = ko.utils.arrayFilter(TestNWJS.QualificationList.ViewModel.AllCredentials(), function (item) {
        return item.IsSelected === true;
    });

    return filtering;
}

function CreateQualificationModel(allCredentialsList) {
    TestNWJS.QualificationList.ViewModel = {};

    TestNWJS.QualificationList.ViewModel.AllCredentials = ko.observableArray(allCredentialsList);

    TestNWJS.QualificationList.ViewModel.UnselectedCredentials = ko.computed(FindUnselectedCredentials, this);
    TestNWJS.QualificationList.ViewModel.SelectedCredentials = ko.computed(FindSelectedCredentials, this);

    TestNWJS.QualificationList.ViewModel.AllCredentials.extend({ notify: 'always' });
    TestNWJS.QualificationList.ViewModel.UnselectedCredentials.extend({ notify: 'always' });
    TestNWJS.QualificationList.ViewModel.SelectedCredentials.extend({ notify: 'always' });
}

function toggleselected(id) {
    var match = ko.utils.arrayFirst(TestNWJS.QualificationList.ViewModel.AllCredentials(), function (item) {
        id = parseInt(id);
        return id === item.Id;
    });

    match.IsSelected = !match.IsSelected;
    return match;
}

//public function
return {

    Init: function (allCredentialsList) {
        CreateQualificationModel(allCredentialsList);

        //when you select something from the dropdown this will happen.
        $("select[name='QualificationFilter']").change(function (e) {
            var id = $(this).val();
            e.preventDefault();
            var form = $(e.target).parents("form");
            var url = window.location.href.substr(0, window.location.href.lastIndexOf("QualificationList") + 17)
            form.attr("action", url + "?Id=" + id);
            form.submit();
        });

        $("#UnselectedCredentialsList").live('dblclick', function (e) {
            toggleselected(this.value);
        });

        $("#SelectedCredentialsList").live('dblclick', function (e) {
            toggleselected(this.value);
        });

        ko.applyBindings(TestNWJS.QualificationList.ViewModel);
    }
}

})();

查看:

<div>
    <table>
        <tr>
            <td class="fieldName_td">
                @Html.Label("Available Credentials")
            </td>
            <td class="fieldData_td">
                <select data-bind="options: UnselectedCredentials,
                                   optionsText: 'Name',
                                   optionsValue: 'Id'"
                                   size="10" multiple="multiple" id="UnselectedCredentialsList"></select>
            </td>
        </tr>
    </table>
</div>

<div>
    <table>
        <tr>
            <td class="fieldName_td">
                @Html.Label("Selected Credentials")
            </td>
            <td class="fieldData_td">
                <select data-bind="options: SelectedCredentials,
                                   optionsText: 'Name',
                                   optionsValue: 'Id'"
                                   size="10" multiple="multiple" id="SelectedCredentialsList"></select>
            </td>
        </tr>
    </table>
</div>
}
@section scripts {
    @Scripts.Render("~/Scripts/knockout-2.2.1.js", "~/jscripts/Administration/Interfaces/QualificationList.js", "~/Scripts/knockout.mapping-latest.js")

<script type="text/javascript">
    $(function () {
        TestNWJS.QualificationList.Init(@Html.Raw(Model.JsonAllCredentials));
    })
</script>

}

澄清一下,在页面初始加载时,"UnselectedCredentials" 列表正确显示(这意味着来自 AllCredentials 数组且 "IsSelected" 值等于 false 的所有凭据(这就是一切一开始)正在出现)。我遇到的问题与视图(和潜在的视图模型)在双击触发器切换值后未正确更新有关。

我认为问题可能出在 JSON 数据的初始加载上。如果我没记错的话,Knockout 不会自动使 JSON 对象的属性可观察。 observableArray 仅查看项目何时添加到数组或从数组中删除,而不查看其中的项目是否发生更改。您可能需要为 JSON 数据编写一个反序列化程序,或者使用构造函数创建一个 Credential class (使所有属性都可观察),您可以将 JSON 数据的元素输入其中在将它们放入可观察数组之前。

调查Knockout Mapping Plugin。这可能会有所帮助

我不确定您是否受到视图模型的限制,但我也建议您稍微简化一下。真的没有理由拥有多个数组并来回移动项目。一个更简单的实现是有一个数组,根据选择 属性 是真还是假在表中显示。像这样:

<div>
    <table>
        <tr>
            <td class="fieldName_td">
                @Html.Label("Available Credentials")
            </td>
            <td class="fieldData_td">
                 <table data-bind="foreach:Credentials">
                     <!-- ko if: !Selected -->
                     <tr>
                         <td data-bind="text><select data-bind="text: Name, $root.click: toggleSelected></td>
                     </tr>
                 </table>
            </td>
        </tr>
    </table>
</div>

<div>
    <table>
        <tr>
            <td class="fieldName_td">
                @Html.Label("Selected Credentials")
            </td>
            <td class="fieldData_td">
                 <table data-bind="foreach:Credentials">
                     <!-- ko if: Selected -->
                     <tr>
                         <td data-bind="text><select data-bind="text: Name, $root.click: toggleSelected></td>
                     </tr>
                 </table>
            </td>
        </tr>
    </table>
</div>
}

在您的视图模型中,将所有凭据放在一个可观察数组中(每个 属性 凭据也应该是可观察的),然后视图模型必须做的唯一工作(只要选定 属性 是可观察的)是

toggleSelection = function(credential) {
    credential.Selected = !credential.Selected;
}

然后,当您需要使用选定的凭据时,只需为选定的凭据过滤数组即可。

祝你好运。

编辑:关于凭证 class,假设唯一的属性是 Name 和 Selected,可能类似于:

function Credential(name, selected) {
    this.Name = ko.observable(name);
    this.Selected = ko.observable(selected);
}

然后您可以循环遍历 JSON 数据数组

 var Credentials = ko.observableArray();
 credentialsFromJson.forEach(function(c) {
     var credential = new Credential(c.Name, c.Selected);
     Credentials.push(credential);
 }

这使得每个 属性 都可以观察到。如果要看的属性比较多,看上面说的ko.mapping插件

这有帮助吗?如果我正确阅读代码,计算数组不会更新,因为 KO 没有观察到任何事情,并且使 Selected 属性 可观察应该可以解决这个问题。