Пользовательская привязка группы переключателей Knockoutjs не обновляется при выделении

Я пытаюсь создать пользовательскую привязку в нокауте, которая работает как 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?

ДЕМО ЗДЕСЬ

1 ответ

Решение

Не похоже, что у вашей привязки есть способ обновить наблюдаемое при изменении выбора. Двусторонняя привязка не является автоматической с пользовательскими привязками, если вы просто не передаете параметры другой существующей привязке. Обработчики событий, такие как ваш "хак", - это именно то, для чего обычно используется функция "init" пользовательской привязки.

Обратный вызов "init"

Knockout будет вызывать вашу функцию init один раз для каждого элемента DOM, для которого вы используете> привязку. Существует два основных варианта использования init:

  • Чтобы установить любое начальное состояние для элемента DOM
  • Чтобы зарегистрировать любые обработчики событий, чтобы, например, когда пользователь щелкает или изменяет элемент DOM, вы могли изменить состояние связанной наблюдаемой

Попробуйте добавить что-то вроде следующей функции 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) {
      ...
  }

Вероятно, есть более безопасные способы обработки события click, чем проверка, является ли цель элементом "input"; это просто быстрый пример. Вам придется изменить это, если у вас когда-либо есть вложенные переключатели или другие типы элементов ввода в качестве дочерних элементов в вашем элементе radiobutton. В случае сомнений вы всегда можете скопировать из нокаута "проверенный" исходный код привязки.

РЕДАКТИРОВАТЬ: Некоторые вещи, чтобы исправить и как я их исправил.

  1. Обновите значение свойства viewmodel.
  2. Обновите представление, когда свойство value программно изменяется (двусторонняя привязка).
  3. Если выбранный переключатель удален, мне нужно установить значение свойства неопределенным.

Вероятно, есть лучший способ добиться всего этого...

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);
      }
  }
}
Другие вопросы по тегам