模板内部选项绑定的奇怪行为
Strange behavior of option binding inside of template
出于某种原因,如果 select 控件是在挖空模板内定义的,我无法 select 选项使用空值,因为 select 会立即将其状态更改为默认值。 注意:如果在外部定义select,它工作得很好。试试吧:
var ViewModel = function(){
var self = this;
self.options = [ null, 1, 2, 3];
self.selectedOption = ko.observable();
self.onSelectionChange = ko.computed(function(){
alert(self.selectedOption());
});
self.items = [{someProp: null, title: "item 1"},
{someProp: 2, title: "item 2"},
{someProp: 1, title: "item 3"},
{someProp: 1, title: "item 4"},
{someProp: 3, title: "item 5"}];
self.filteredItems = ko.computed(function(){
return self.items.filter(function(item){
return item.someProp === self.selectedOption();
});
});
};
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="template: { name: 'list-template', data: filteredItems }"></div>
<script id="list-template" type="text/html">
<select data-bind="options: $root.options,
optionsCaption: 'select...',
value: $root.selectedOption"></select>
<ul data-bind="foreach: $data">
<li data-bind="text: title"></li>
</ul>
</script>
谁能解释一下为什么会这样?我是不是应该明白点什么,或者这似乎是淘汰赛的问题?
更新:这段代码只是真实代码的浓缩示例。没有意义告诉我这是毫无意义的;)
我只是在 table 中有一些记录,其中一些列已按某些字段进行过滤。 我需要 select 可以同时具有欠定义和空值,因为未定义意味着过滤器处于非活动状态,而 null 意味着我应该显示所有具有未设置字段的记录。
在您的示例中发生了 很多 奇怪的事情,包括:
- 你有一个项目数组,每个项目都有一个 属性 称为 "key",但是有重复的键(那不是真正的 key 那么,是它?);
- 您有一个
computed
冒充事件处理程序或更改订阅;
- 模板中有
select
,但原因不明,因为它没有在上下文中引用任何 $data
;
options
和 items
的列表似乎有相同的目的;
- 尽管您打算用 null 初始化 observable,但您没有初始化可观察对象(使用
undefined
有效)。但是,null 有点特殊,如果您尝试使 null
成为一个显式值,我不希望 KO 的行为是可预测的(至少对我而言)。使用您已经拥有的显式空对象(self.items
中的第一个对象)更有意义;
但是,如果不直接回答您的问题(我不能,但其他人可以?),我仍然可以为您提供另一种处理问题的方式。使用整个 item
作为您选择的项目。
这是一个 IMO 可以预测的例子:
var ViewModel = function() {
var self = this;
self.options = [
{ key: null, value: "null item" },
{ key: 1, value: "item 1a" },
{ key: 1, value: "item 1b" },
{ key: 2, value: "item 2" }
];
self.selectedOption = ko.observable(self.options[0]);
self.filteredItems = ko.computed(function() {
return self.options.filter(function(item) {
return item.key === self.selectedOption().key;
});
});
};
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="template: { name: 'list-template', data: filteredItems }"></div>
<script id="list-template" type="text/html">
<select data-bind="options: $root.options,
optionsText: 'value',
value: $root.selectedOption"></select>
<p>All items with the same key:</p>
<ul data-bind="foreach: $data">
<li data-bind="text: value"></li>
</ul>
</script>
<hr> SelectedOption: <strong data-bind="text: ko.toJSON($root.selectedOption)"></strong>
Knockout 在将它们写入 select.
时将 undefined、null 和空字符串视为相同的值
请参阅下面的 writeValue:
ko.selectExtensions = {
readValue : function(element) {
switch (ko.utils.tagNameLower(element)) {
case 'option':
if (element[hasDomDataExpandoProperty] === true)
return ko.utils.domData.get(element, ko.bindingHandlers.options.optionValueDomDataKey);
return ko.utils.ieVersion <= 7
? (element.getAttributeNode('value') && element.getAttributeNode('value').specified ? element.value : element.text)
: element.value;
case 'select':
return element.selectedIndex >= 0 ? ko.selectExtensions.readValue(element.options[element.selectedIndex]) : undefined;
default:
return element.value;
}
},
writeValue: function(element, value, allowUnset) {
switch (ko.utils.tagNameLower(element)) {
case 'option':
switch(typeof value) {
case "string":
ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, undefined);
if (hasDomDataExpandoProperty in element) { // IE <= 8 throws errors if you delete non-existent properties from a DOM node
delete element[hasDomDataExpandoProperty];
}
element.value = value;
break;
default:
// Store arbitrary object using DomData
ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, value);
element[hasDomDataExpandoProperty] = true;
// Special treatment of numbers is just for backward compatibility. KO 1.2.1 wrote numerical values to element.value.
element.value = typeof value === "number" ? value : "";
break;
}
break;
case 'select':
if (value === "" || value === null) // A blank string or null value will select the caption
value = undefined;
var selection = -1;
for (var i = 0, n = element.options.length, optionValue; i < n; ++i) {
optionValue = ko.selectExtensions.readValue(element.options[i]);
// Include special check to handle selecting a caption with a blank string value
if (optionValue == value || (optionValue == "" && value === undefined)) {
selection = i;
break;
}
}
if (allowUnset || selection >= 0 || (value === undefined && element.size > 1)) {
element.selectedIndex = selection;
}
break;
default:
if ((value === null) || (value === undefined))
value = "";
element.value = value;
break;
}
}
};
您必须使用与 null 不同的密钥(-1 是常见的选择)
出于某种原因,如果 select 控件是在挖空模板内定义的,我无法 select 选项使用空值,因为 select 会立即将其状态更改为默认值。 注意:如果在外部定义select,它工作得很好。试试吧:
var ViewModel = function(){
var self = this;
self.options = [ null, 1, 2, 3];
self.selectedOption = ko.observable();
self.onSelectionChange = ko.computed(function(){
alert(self.selectedOption());
});
self.items = [{someProp: null, title: "item 1"},
{someProp: 2, title: "item 2"},
{someProp: 1, title: "item 3"},
{someProp: 1, title: "item 4"},
{someProp: 3, title: "item 5"}];
self.filteredItems = ko.computed(function(){
return self.items.filter(function(item){
return item.someProp === self.selectedOption();
});
});
};
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="template: { name: 'list-template', data: filteredItems }"></div>
<script id="list-template" type="text/html">
<select data-bind="options: $root.options,
optionsCaption: 'select...',
value: $root.selectedOption"></select>
<ul data-bind="foreach: $data">
<li data-bind="text: title"></li>
</ul>
</script>
谁能解释一下为什么会这样?我是不是应该明白点什么,或者这似乎是淘汰赛的问题?
更新:这段代码只是真实代码的浓缩示例。没有意义告诉我这是毫无意义的;) 我只是在 table 中有一些记录,其中一些列已按某些字段进行过滤。 我需要 select 可以同时具有欠定义和空值,因为未定义意味着过滤器处于非活动状态,而 null 意味着我应该显示所有具有未设置字段的记录。
在您的示例中发生了 很多 奇怪的事情,包括:
- 你有一个项目数组,每个项目都有一个 属性 称为 "key",但是有重复的键(那不是真正的 key 那么,是它?);
- 您有一个
computed
冒充事件处理程序或更改订阅; - 模板中有
select
,但原因不明,因为它没有在上下文中引用任何$data
; options
和items
的列表似乎有相同的目的;- 尽管您打算用 null 初始化 observable,但您没有初始化可观察对象(使用
undefined
有效)。但是,null 有点特殊,如果您尝试使null
成为一个显式值,我不希望 KO 的行为是可预测的(至少对我而言)。使用您已经拥有的显式空对象(self.items
中的第一个对象)更有意义;
但是,如果不直接回答您的问题(我不能,但其他人可以?),我仍然可以为您提供另一种处理问题的方式。使用整个 item
作为您选择的项目。
这是一个 IMO 可以预测的例子:
var ViewModel = function() {
var self = this;
self.options = [
{ key: null, value: "null item" },
{ key: 1, value: "item 1a" },
{ key: 1, value: "item 1b" },
{ key: 2, value: "item 2" }
];
self.selectedOption = ko.observable(self.options[0]);
self.filteredItems = ko.computed(function() {
return self.options.filter(function(item) {
return item.key === self.selectedOption().key;
});
});
};
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="template: { name: 'list-template', data: filteredItems }"></div>
<script id="list-template" type="text/html">
<select data-bind="options: $root.options,
optionsText: 'value',
value: $root.selectedOption"></select>
<p>All items with the same key:</p>
<ul data-bind="foreach: $data">
<li data-bind="text: value"></li>
</ul>
</script>
<hr> SelectedOption: <strong data-bind="text: ko.toJSON($root.selectedOption)"></strong>
Knockout 在将它们写入 select.
时将 undefined、null 和空字符串视为相同的值请参阅下面的 writeValue:
ko.selectExtensions = {
readValue : function(element) {
switch (ko.utils.tagNameLower(element)) {
case 'option':
if (element[hasDomDataExpandoProperty] === true)
return ko.utils.domData.get(element, ko.bindingHandlers.options.optionValueDomDataKey);
return ko.utils.ieVersion <= 7
? (element.getAttributeNode('value') && element.getAttributeNode('value').specified ? element.value : element.text)
: element.value;
case 'select':
return element.selectedIndex >= 0 ? ko.selectExtensions.readValue(element.options[element.selectedIndex]) : undefined;
default:
return element.value;
}
},
writeValue: function(element, value, allowUnset) {
switch (ko.utils.tagNameLower(element)) {
case 'option':
switch(typeof value) {
case "string":
ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, undefined);
if (hasDomDataExpandoProperty in element) { // IE <= 8 throws errors if you delete non-existent properties from a DOM node
delete element[hasDomDataExpandoProperty];
}
element.value = value;
break;
default:
// Store arbitrary object using DomData
ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, value);
element[hasDomDataExpandoProperty] = true;
// Special treatment of numbers is just for backward compatibility. KO 1.2.1 wrote numerical values to element.value.
element.value = typeof value === "number" ? value : "";
break;
}
break;
case 'select':
if (value === "" || value === null) // A blank string or null value will select the caption
value = undefined;
var selection = -1;
for (var i = 0, n = element.options.length, optionValue; i < n; ++i) {
optionValue = ko.selectExtensions.readValue(element.options[i]);
// Include special check to handle selecting a caption with a blank string value
if (optionValue == value || (optionValue == "" && value === undefined)) {
selection = i;
break;
}
}
if (allowUnset || selection >= 0 || (value === undefined && element.size > 1)) {
element.selectedIndex = selection;
}
break;
default:
if ((value === null) || (value === undefined))
value = "";
element.value = value;
break;
}
}
};
您必须使用与 null 不同的密钥(-1 是常见的选择)