2

我正在尝试在敲除中创建一个自定义绑定,其功能类似于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

在这里演示

4

1 回答 1

1

看起来您的绑定没有办法在选择更改时更新可观察对象。自定义绑定不会自动进行双向绑定,除非您只是将参数传递给另一个现有绑定。像您的“hack”这样的事件处理程序正是自定义绑定的“init”函数通常用于的用途。

“初始化”回调

Knockout 将为您使用的每个 DOM 元素调用一次 init 函数 > 绑定。init有两个主要用途:

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

尝试将类似以下的 init 函数添加到您的 radioButtons 绑定中:

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) {
      ...
  }

处理点击事件可能比检查目标是否是“输入”元素更安全;这只是一个简单的例子。如果您曾经在单选按钮元素中有嵌套的单选按钮或其他类型的输入元素作为子元素,则必须对其进行修改。如果有疑问,您可以随时从淘汰赛“检查”绑定源代码中复制。

编辑:一些要修复的东西以及我如何修复它们。

  1. 更新视图模型的 value 属性。
  2. 当 value 属性以编程方式更改时更新视图(双向绑定)。
  3. 如果选中的单选按钮被删除,我需要将 value 属性设置为 undefined。

可能有更好的方法来实现所有这些......

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);
      }
  }
}
于 2017-04-06T22:43:45.197 回答