Пользовательская привязка группы переключателей 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. В случае сомнений вы всегда можете скопировать из нокаута "проверенный" исходный код привязки.
РЕДАКТИРОВАТЬ: Некоторые вещи, чтобы исправить и как я их исправил.
- Обновите значение свойства viewmodel.
- Обновите представление, когда свойство value программно изменяется (двусторонняя привязка).
- Если выбранный переключатель удален, мне нужно установить значение свойства неопределенным.
Вероятно, есть лучший способ добиться всего этого...
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);
}
}
}