Knockoutjs单选按钮组自定义绑定未在选择

时间:2017-04-06 00:32:25

标签: javascript knockout.js custom-binding ko-custom-binding

我正在尝试在knockout中创建一个自定义绑定,其功能类似于options默认绑定处理程序,但使用单选按钮而不是下拉列表。

向数组添加项目将通知update,但不会选择其他单选按钮。

注意:我将自定义绑定处理程序剥离到最低限度。

绑定处理程序

// LOOK FOR MY COMMENT //<------------------------------ LOOK FOR IT
ko.bindingHandlers.radioButtons = {
    update: function (element, valueAccessor, allBindings) {
        var unwrappedArray = ko.utils.unwrapObservable(valueAccessor());
        var previousSelectedValue = ko.utils.unwrapObservable(allBindings().value);


        // Helper function
        var applyToObject = function (object, predicate, defaultValue) {
            var predicateType = typeof predicate;
            if (predicateType == "function")    // Given a function; run it against the data value
                return predicate(object);
            else if (predicateType == "string") // Given a string; treat it as a property name on the data value
                return object[predicate];
            else                                // Given no optionsText arg; use the data value itself
                return defaultValue;
        };

        // Map the array to a newly created label and input.
        var isFirstPass = true;
        var radioButtonForArrayItem = function (arrayEntry, index, oldOptions) {
            if (oldOptions.length) {
                var item = oldOptions[0];
                if ($(item).is(":checked"))
                    previousSelectedValue = item.value;

                isFirstPass = false;
            }

            var input = element.ownerDocument.createElement("input");
            input.type = "radio";

            var radioButtonGroupName = allBindings.get("groupName");
            input.name = radioButtonGroupName;

            if (isFirstPass) {
                var selectedValue = ko.utils.unwrapObservable(allBindings.get("value"));
                var itemValue = ko.utils.unwrapObservable(arrayEntry.value);
                if (selectedValue === itemValue) {
                    input.checked = true;
                }
            } else if ($(oldOptions[0].firstElementChild).is(":checked")) {
                input.checked = true;
            }

            var radioButtonValue = applyToObject(arrayEntry, allBindings.get("radioButtonValue"), arrayEntry);
            ko.selectExtensions.writeValue(input, ko.utils.unwrapObservable(radioButtonValue));

            var label = element.ownerDocument.createElement("label");
            label.appendChild(input);
            var radioButtonText = applyToObject(arrayEntry, allBindings.get("radioButtonText"), arrayEntry);
            label.append(" " + radioButtonText);

            return [label];
        };

        var setSelectionCallback = function (arrayEntry, newOptions) {
            var inputElement = newOptions[0].firstElementChild;
            if ($(inputElement).is(":checked")) {
                var newValue = inputElement.value;
                if (previousSelectedValue !== newValue) {
                    var value = allBindings.get("value");
                    value(newValue); //<------------------------- Get observable value and set here. Shouldn't this notify the update?
                }
            }
        };

        ko.utils.setDomNodeChildrenFromArrayMapping(element, unwrappedArray, radioButtonForArrayItem, {}, setSelectionCallback);
    },
};

如何使用它......

// Html
<div data-bind="
    radioButtons: letters, 
    groupName: 'letters', 
    radioButtonText: 'text', 
    radioButtonValue: 'value',   
    value: value,      
"></div>

// Javascript
var vm = {
    letters: ko.observableArray([
        {
            text: "A",
            value: 1,
        },
        {
            text: "B",
            value: 2,
        },
    ]),
    value: ko.observable(2),
};

ko.applyBindings(vm);

因此,向vm.letters({ text: "C", value: 2 })添加新项目会通知update。但是,单击其他单选按钮不会通知update

我需要做什么才能点击单选按钮会通知update

DEMO HERE

1 个答案:

答案 0 :(得分:1)

当选择更改时,您的绑定看起来不像是更新observable的方法。除非您只是将参数传递给另一个现有绑定,否则双向绑定不会自动与自定义绑定。像“hack”这样的事件处理程序正是通常用于自定义绑定的“init”功能的。

  

“init”回调

     

Knockout将为每个DOM元素调用一次init函数      你使用&gt;绑定。 init有两个主要用途:

     
      
  • 设置DOM元素的任何初始状态
  •   
  • 要注册任何事件处理程序,以便,例如,当用户单击或修改DOM元素时,您可以更改关联的可观察对象的状态
  •   

尝试在radioButtons绑定中添加类似以下init函数的内容:

ko.bindingHandlers.radioButtons = {
  init: function(element, valueAccessor, allBindings){
    var unwrappedArray = ko.utils.unwrapObservable(valueAccessor());
    var value = allBindings().value;

    ko.utils.registerEventHandler(element, "click", function(event){
        var target = event.target;
        if(target.nodeName.toLowerCase() == "input"){
            value($(target).attr("value"));
        }
    });
  },
  update: function (element, valueAccessor, allBindings) {
      ...
  }

处理click事件可能比检查目标是否为“input”元素更安全。这只是一个简单的例子。如果你的单选按钮或其他类型的输入元素作为radiobutton元素中的子元素,则必须修改它。如果有疑问,您可以随时从淘汰赛中复制“已检查”约束source code

编辑:需要修复的一些内容以及修复方法。

  1. 更新viewmodel的value属性。
  2. 以编程方式更改value属性时更新视图(双向绑定)。
  3. 如果删除了所选的单选按钮,我需要将value属性设置为undefined。
  4. 可能有更好的方法来实现这一切......

    ko.bindingHandlers.radioButtons = {
      init: function(element, valueAccessor, allBindings){
        //... error checks ... Remove all child elements to "element ... 
    
        var value = allBindings.get("value");
    
        // 1. Update viewmodel
        ko.utils.registerEventHandler(element, "click", function(event){
            var target = event.target;
            if(target.nodeName.toLowerCase() == "input"){
                value(target.value);
            }
        });
    
        // 2. Update view 
        value.subscribe(function (newValue) {
            var inputs = element.getElementsByTagName("input")
            $.each(inputs, function (i, input) {
                input.checked = input.value === newValue;
            });
        };
      },
      update: function (element, valueAccessor, allBindings) {
          ...
    
          var value = allBindings.get("value");
    
          // 3. Edge case: remove radio button of the value selected.
          var selectedRadioButton = unwrappedArray.find(function (item) { 
              return item.value === value(); 
          });
          if (selectedRadioButton == null) {
              value(undefined);
          }
      }
    }