敲除计算数组未正确更新
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 属性 可观察应该可以解决这个问题。
我很难根据我的视图模型更新我的视图。总结一下我在做什么:我有一组项目 (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 属性 可观察应该可以解决这个问题。