模板内部选项绑定的奇怪行为

时间:2016-02-02 14:30:08

标签: javascript knockout.js knockout-3.0

由于某些原因,如果在挖空模板内部定义了选择控件,我无法选择具有空值的选项,因为选择立即将其状态更改为默认值。 注意:如果在外面定义了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>

任何人都能解释一下为什么会这样吗?我应该理解什么,或者它似乎是淘汰赛问题?

更新:此代码只是简单的样本。没有任何意义告诉我这是毫无意义的;) 我只是在表中有一些记录,其中一些列有一些字段过滤。并且我需要select可以同时具有欠定义和空值,因为undefined意味着过滤器处于非活动状态,而null意味着我应该显示所有具有未设置字段的记录。

2 个答案:

答案 0 :(得分:1)

您的示例中有一些很多的奇怪事情,包括:

  • 你有一个项目数组,每个项目名为&#34; key&#34;,但是有重复的键(那不是那么,是吗? );
  • 您有computed冒充事件处理程序或更改订阅;
  • 您在模板中有select,但目前还不清楚为什么,因为它没有引用上下文中$data的任何内容;
  • optionsitems的列表似乎用于同一目的;
  • 你没有任何东西(有效地使用undefined)初始化observable,尽管你的意思是用null初始化它。但是,null有点特殊,如果你试图使null成为显式值,我不会期望KO可预测(至少对我来说)。使用已有的显式Null对象更有意义(self.items中的第一个对象);

然而,如果没有直接回答你的问题(我不能,但其他人可能会这样做?),我仍然可以提供另一种处理事情的方法。使用整个item作为您选择的项目。

这是一个可预测的IMO示例:

&#13;
&#13;
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());
&#13;
<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>
&#13;
&#13;
&#13;

答案 1 :(得分:1)

Knockout将undefined,null和空字符串视为将它们写入select时的相同值。

请参阅下面的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是常见选项)